信息学竞赛NOIP2018复赛提高组day2题目解答
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
提高组 day2
来自百度文库
全国信息学奥林匹克联赛(NOIP2018)复赛 for(int i=1;i<=m;i++) { delx=edge[i].x; dely=edge[i].y; ret[0]=1; memset(vis,0,sizeof(vis)); vis[1]=1; ret[1]=1; dfs(1); if(ret[0]!=n) continue; for(int j=1;j<=n;j++) { if(ret[j]<ans[j]) { for(int k=1;k<=n;k++) ans[k]=ret[k]; break; } if(ret[j]>ans[j]) break; } } } else { vis[1]=1; ret[0]=1; ret[1]=1; dfs(1); for(int i=1;i<=n;i++) ans[i]=ret[i]; }
提高组 day2
全国信息学奥林匹克联赛(NOIP2018)复赛 for(int i=1;i<=n;i++) printf("%d ",ans[i]); printf("\n"); return 0; }
提高组 day2
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
2. 填数游戏 (game.cpp/c/pas) 【问题模型】 给定一个 n*m 的矩形表格,每个格子可以填入 0 或 1 两个字符。表格的路 径定义为从(0,0)不断向右或向下走到(n-1,m-1)的路线。 定义路径上所有决策(R、D 字符)组成的字符串为 s,所有途径字符(0、 1 字符)组成的字符串为 w。定义表格的字符填充定义为合法的,当且仅当对于 表格中的任意两条路径和,如果满足,则必须。求合法字符填充的方案数。 【问题分析】 对于本题,从题目中“在表格中填充 01”的描述联想到的算法是状态压缩 动态规划。但是我们观察题目的数据范围,n 的最大值为 8,符合状压 dp 的数 据量,而 m 的最大值则高达 1000000,差距很大。从数据中我们容易联想到的 做法是对 m 比较大的情况进行找规律求解。 【算法一】 容易想到的算法是暴力对表格进行填表和验证。我们暴力枚举表格的每个 位置填充的字符,时间复杂度为,枚举任意两条路径判断是否满足题设条件, 时间复杂度为,总时间复杂度为,期望得分 20。 【算法二】 我们考虑对算法一进行优化。我们首先考虑剪掉不必要的枚举状态,观察 题目中的限制条件,对于任意两条路径和,如果满足,则两条路径一定在表格 中的某个点向右走,而向下走,此时由于条件的限制,右上方的点所填字符一 定小于等于左下方的点所填字符。由于路径和的任意性,在每条对角线上,从 右上方到左下方所填字符一定不严格地单调递增。我们可以在递归时判断是否 满足条件,不满足时立即回溯,由于这一限制比较严格,这一剪枝极大地减少 了枚举的范围。
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
【参考答案】
#include<bits/stdc++.h> using namespace std; const int maxn=5050; int *a[maxn]; int D[maxn]; int delx,dely; int ret[maxn]; int ans[maxn]; struct aa { int x,y; }edge[maxn]; int vis[5005]; bool pd(int x,int y) { if(x==delx&&y==dely) return 1; if(x==dely&&y==delx) return 1; return 0; } void dfs(int x) { for(int i=1;i<=a[x][0];i++) if(!vis[a[x][i]]&&!pd(x,a[x][i])) { vis[a[x][i]]=1; ret[0]++; ret[ret[0]]=a[x][i]; dfs(a[x][i]);
提高组 day2
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
3. 保卫王国 (defense.cpp/c/pas) 【问题模型】 给出一棵带有点权的树,求出它的最小权点覆盖集。每次更新两个树上的 点 a,b,表示 a 和 b 必须在或者不在点覆盖集中。每次更新之后需要输出当前 的答案,无解输出-1。 【问题分析】 树是一种特殊的二分图,最小权点覆盖集可以转化为所有边权之和最大权 点独立集。 当指定某个点必须在覆盖集中时,我们可以修改这个点的点权为 0,根据 贪心选择性质,选这个点结果一定不会更差。当指定某个点必须不在覆盖集中 时,可以修改这个点的点权为无穷,这样,当有其他解时,一定不选这个点。 如果计算出的答案大于等于无穷,说明无其他解,应当输出-1。 至此,本题被转化成了树上动态最大点权独立集的问题。 【问题求解】 树上最大权独立集是一个简单的树上动态规划问题;而树上动态最大点权 独立集是一个动态规划套动态规划(动态 dp)的模板题。我们可以采用用平衡 树/LCT/树链剖分等方法维护每个点上的状态转移矩阵,这样就可以做到在修改 参数的同时维护这个点对于全局动态规划的影响。
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
【参考答案】
#include<cstring> #include<cmath> #include<cstdio> #include<iostream> #include<algorithm> using namespace std;
typedef long long ll;
提高组 day2
全国信息学奥林匹克联赛(NOIP2018)复赛 }*S[maxn];
提高组 day2
全国信息学奥林匹克联赛(NOIP2018)复赛 M z(INF); for(int i=0;i<=1;i++) for(int j=0;j<=1;j++) for(int k=0;k<=1;k++) z.a[i][k]=min(z.a[i][k],a[i][j]+y.a[j][k]); return z; } }; struct lnk { lnk *last; long long x1,x2; lnk *left,*right; M now; lnk() { left=NULL; right=NULL; last=NULL; } void Trans() { now.a[0][0]=INF; now.a[1][0]=x2; now.a[1][1]=x2; now.a[0][1]=x1; if(right!=0) now=now*(right->now); if(left!=0) now=(left->now)*now; }
全国信息学奥林匹克联赛(NOIP2018)复赛 } } int main() { freopen("travel.in","r",stdin); freopen("travel.out","w",stdout); int n, m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&edge[i].x,&edge[i].y); D[edge[i].x]++; D[edge[i].y]++; } for(int i=1;i<=n;i++) { a[i]=new int[D[i]+1]; a[i][0]=D[i]; } for(int i=1;i<=m;i++) { int x=edge[i].x,y=edge[i].y; a[x][D[x]--]=y; a[y][D[y]--]=x; } for(int i=1;i<=n;i++) sort(a[i]+1,a[i]+a[i][0]+1); if(m==n) { ans[1]=2;
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
1. 旅行 (travel.cpp/c/pas) 【问题模型】 给出一个树或者是带环树(即 n 个点 n 条边的连通图) ,求出最小字典序的 DFS 序。 【问题分析与求解】 如果输入为一棵树,那么从 1 号点出发,每次总是先找编号更小的子节点 即可求解。 如果输入为一棵带环树,这个做法就会出现反例。考虑到本题可以用平方 的复杂度通过,我们可以删掉一条边,如果删边之后图仍然连通,那么此时图 中还有 n 个点 n-1 条边,一定转化成了一个树。因此我们可以直接枚举删哪条 边,即可用上述方法得到最小字典序的答案。 时间复杂度: 【注意事项】 本题时间复杂度较高。需要尽可能降低程序求解中的常数。降低常数的方 法包括快速输入输出、使用动态开点的数组存图等。
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
【参考答案】
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+50; const long long INF=1e13; int n,m; int P[maxn],last[maxn]; vector<int>E[maxn]; int Buffer[50]; char sss[10]; struct M { long long a[2][2]; M() { a[0][0]=0; a[0][1]=0; a[1][0]=0; a[1][1]=0; } M(long long v) { a[0][0]=v; a[0][1]=v; a[1][0]=v; a[1][1]=v; } M operator *(const M &y) {
int n,m;
ll qpow(ll a,ll x) { ll t=1; while(x) { if(x&1) t=a*t%MOD; a=a*a%MOD; x/=2; } return t; }
int main()
全国信息学奥林匹克联赛(NOIP2018)复赛 { freopen("game.in","r",stdin); freopen("game.out","w",stdout); cin>>n>>m; if(n>m) swap(n,m); if(n==1) cout<<qpow(2,m)<<endl; else { if(n==m) cout<<vn[n]<<endl; else cout<<vm[n]*qpow(3,m-n-1)%MOD<<endl; } return 0; }
全国信息学奥林匹克联赛(NOIP2018)复赛
提高组 day2
我们继续考虑验证部分的时间复杂度。对于小数据的情况,由于路径长度 等于 n+m-1,我们可以采用 int 类型记录每条路径,在验证过程中,采用贪心 策略对每一个点计算当前点到终点的字串最大值和最小值,对于不在最后一行 和最后一列的点,由于需要满足“向下走”比“向右走”的字典序更大,因此 这些点都要保证向右走的最大值小于等于向下走的最小值。容易证明,上述条 件与题目条件等价。验证部分的时间复杂度为。 经过枚举部分和验证部分的优化,程序的时间复杂度大幅降低。如果实际 完成的程序常数较大,在 n 和 m 的值为 8 时难以在 1 秒内得出结果,可以在本 地计算出 n 和 m 不超过 8 的所有结果,将结果保存在程序中直接输出结果。期 望得分 35。 【算法三】 由于 m 的范围过大,本题我们考虑对 m 比较大的情况进行找规律求解。通 过算法二我们可以生成一个 n,m 范围较小的矩阵进行观察。我们发现在时, , 即答案为 m 的等比数列。因此我们只需在算法二打表的基础上采用上述递推公 式进行求解。期望得分 100。
const int MOD=1000000007; const int vn[10]={0,0,12,112,912,7136,56768,453504,3626752}; const int vm[10]={0,0,36,336,2688,21312,170112,1360128,10879488};