NOIp 2010 解题报告
1. 机器翻译
可以看出这是一道队列的模拟题目。按照题目要求模拟翻译过程即可。
复杂度O(N)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 8 //variable// 9 int q[1010],n,m,vis[1010]; 10 11 //solve// 12 int main(){ 13 int ans=0; 14 scanf("%d%d",&m,&n); 15 int he=1,ta=0; 16 for (int i=1;i<=n;++i){ 17 int x; 18 scanf("%d",&x); 19 if (vis[x]) continue; 20 vis[x]=1; 21 ++ans; 22 if (ta-he+1==m){ 23 vis[q[he++]]=0; 24 } 25 q[++ta]=x; 26 } 27 printf("%d\n",ans); 28 return 0; 29 }
2. 乌龟棋
可以想到是动态规划,那么问题是如何定义状态。
状态定义的原则是:依据所给的状态描述,所有实际状态必须唯一。
而上文提到的实际状态的含义是:题目所给的相关量(不包括所求量)的值。比如在这一题而言,就是4种卡片的数量和到达的位置。
只有满足上述原则,才能利用局部最优推导下一个局部最优直至整体最优。即,对于这道题而言,定义状态为“棋子所在的位置”是一定不可行的,因为对于一个位置x,到达它所耗的卡片不唯一,则剩余卡片也不唯一,实际状态就不唯一,无法推出最终答案。
找到满足上述原则的状态定义实际不难。方法是:让无法通过计算得到的实际状态加入到状态描述中(即我们开的数组)。
对于这道题,我们发现可以用四个卡片的使用数来描述状态。即定义f[i][j][k][t]代表使用i张1卡片,j张2卡片,k张3卡片,t张4卡片所能得到的最大价值。
转移也就很简单了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 8 //variable// 9 int n,m,a[400],tot[5],f[45][45][45][45]; 10 11 //solve// 12 int main(){ 13 scanf("%d%d",&n,&m);--n; 14 for (int i=0;i<=n;++i){ 15 scanf("%d",a+i); 16 } 17 for (int i=1;i<=m;++i){ 18 int x; 19 scanf("%d",&x); 20 ++tot[x]; 21 } 22 f[0][0][0][0]=a[0]; 23 for (int i=0;i<=tot[1];++i){ 24 for (int j=0;j<=tot[2];++j){ 25 for (int k=0;k<=tot[3];++k){ 26 for (int t=0;t<=tot[4];++t){ 27 int maxx=f[i][j][k][t],loc=i+j*2+k*3+t*4; 28 if (i) maxx=max(maxx,f[i-1][j][k][t]+a[loc]); 29 if (j) maxx=max(maxx,f[i][j-1][k][t]+a[loc]); 30 if (k) maxx=max(maxx,f[i][j][k-1][t]+a[loc]); 31 if (t) maxx=max(maxx,f[i][j][k][t-1]+a[loc]); 32 f[i][j][k][t]=maxx; 33 } 34 } 35 } 36 } 37 printf("%d\n",f[tot[1]][tot[2]][tot[3]][tot[4]]); 38 return 0; 39 }
3. 关押罪犯
由于他要求最大的怨气值最小,于是想到二分。
将怨气从大到小排序,二分,将二分到的前面的罪犯组两两连边,BFS染色看存不存在奇环(即能否构成二分图)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 8 //type// 9 struct rec{ 10 int from,dest,cost; 11 }; 12 13 //variable// 14 int n,m; 15 int last[20010],prel[200010],dest[200010],vis[20010],dep[20010],q[100000],tot=1; 16 rec g[100100]; 17 18 //function prototype// 19 bool comp(rec,rec); 20 void calc_ans(void); 21 void addline(int,int); 22 bool bfs(int); 23 bool judge(int); 24 25 //solve// 26 int main(){ 27 scanf("%d%d",&n,&m); 28 for (int i=1;i<=m;++i){ 29 scanf("%d%d%d",&g[i].from,&g[i].dest,&g[i].cost); 30 } 31 sort(g+1,g+m+1,comp); 32 calc_ans(); 33 return 0; 34 } 35 36 void calc_ans(){ 37 int l=1,r=m; 38 while (l<=r){ 39 int mid=l+r>>1; 40 if (judge(mid)){ 41 l=mid+1; 42 }else{ 43 r=mid-1; 44 } 45 } 46 printf("%d\n",g[l].cost); 47 } 48 49 bool judge(int x){ 50 memset(last,0,sizeof last);tot=1; 51 for (int i=1;i<=x;++i){ 52 addline(g[i].from,g[i].dest); 53 addline(g[i].dest,g[i].from); 54 } 55 memset(vis,0,sizeof vis); 56 memset(dep,0,sizeof dep); 57 bool flag=true; 58 for (int i=1;i<=n;++i){ 59 if (!vis[i]){ 60 bool f=bfs(i); 61 flag=(flag&&f); 62 if (!flag) break; 63 } 64 } 65 return flag; 66 } 67 68 bool bfs(int x){ 69 int he=1,ta=1; 70 q[1]=x;vis[x]=1;dep[x]=0; 71 while (he<=ta){ 72 int u=q[he]; 73 q[he++]=0; 74 for (int k=last[u];k;k=prel[k]){ 75 if (!vis[dest[k]]){ 76 vis[dest[k]]=1; 77 dep[dest[k]]=dep[u]^1; 78 q[++ta]=dest[k]; 79 }else{ 80 if (dep[u]==dep[dest[k]]){ 81 return false; 82 } 83 } 84 } 85 } 86 return true; 87 } 88 89 void addline(int u,int v){ 90 dest[++tot]=v; 91 prel[tot]=last[u]; 92 last[u]=tot; 93 } 94 95 bool comp(rec a,rec b){ 96 return a.cost>b.cost; 97 }
4. 引水入城
易证:当能满足要求时,临湖一侧的点i所能引水到的区域一定是连续的区间。如果不是,则一定不满足都建有水利设施的要求。
首先将所有湖泊一侧都装上蓄水厂,BFS,判定是否无解。无解输出即可,有解进入下一步。该过程复杂度O(NM)
设湖泊一侧点i覆盖的沙漠一侧的区间为[ l[i],r[i] ],则答案变成了动态规划问题: f[i]=min{f[l[j]-1]+1,f[i]}(l[j]≤i≤r[j])。这个过程复杂度O(M2)。
下面问题是如何求l[i]和r[i]。
如果对每个湖泊侧的点BFS的话复杂度为O(NM2),有90分。
由于区间连续,我们可以从沙漠侧向湖泊侧DFS:
首先求L数组:1到m枚举DFS的起始点。DFS过程中将走过的点打上标记。如果下一个点走到了标记过的点,回溯,因为我们求L数组,一定是要找可覆盖区间的最小右标号,对于一个已经走过的点(x,y),先前已经有沙漠一侧的点j<i到达过这里,则由(x,y)能DFS到的湖泊侧点的左端点一定不大于j,则对一个i>j,再走点(x,y)也得不到更小的L。
如是优化,同理逆序循环求R,O(NM)求L和R。
至此,本题目完美解决。总复杂度O(NM+M2)
1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 #include<cstdio> 5 using namespace std; 6 7 //constant// 8 const int xy[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; 9 10 //type// 11 struct apair{ 12 int l,r; 13 }g[520]; 14 15 struct node{ 16 int x,y; 17 }q[300000]; 18 19 //variable// 20 int n,m,now; 21 int a[520][520],f[520]; 22 bool p[520][520]; 23 24 //function prototype// 25 void bfs(void); 26 void dfs(int,int); 27 28 //solve// 29 int main(){ 30 scanf("%d%d",&n,&m); 31 for (int i=1;i<=n;i++) 32 for (int j=1;j<=m;j++) 33 scanf("%d",&a[i][j]); 34 bfs(); 35 int rest=0; 36 for (int i=1;i<=m;i++) 37 if (!p[n][i]) 38 rest++; 39 if (rest){ 40 puts("0"); 41 printf("%d\n",rest); 42 return 0; 43 } 44 puts("1"); 45 for (int i=1;i<=m;i++){ 46 memset(p,0,sizeof(p)); 47 now=i; 48 g[now].l=m+1; 49 g[now].r=0; 50 dfs(1,i); 51 } 52 f[0]=0; 53 for (int i=1;i<=m;i++){ 54 f[i]=1<<30; 55 for (int j=1;j<=m;j++) 56 if (i>=g[j].l&&i<=g[j].r) 57 f[i]=min(f[i],f[g[j].l-1]+1); 58 } 59 printf("%d\n",f[m]); 60 } 61 62 void bfs(){ 63 int h=0,t=0; 64 for (int i=1;i<=m;i++){ 65 p[1][i]=true; 66 q[++t].x=1; 67 q[t].y=i; 68 } 69 while (h<t){ 70 node u=q[++h]; 71 for (int i=0;i<4;i++){ 72 node v; 73 v.x=u.x+xy[i][0]; 74 v.y=u.y+xy[i][1]; 75 if (v.x>n||v.x<1||v.y>m||v.y<1) continue; 76 if (a[u.x][u.y]<=a[v.x][v.y]) continue; 77 if (p[v.x][v.y]) continue; 78 p[v.x][v.y]=true; 79 q[++t]=v; 80 } 81 } 82 } 83 84 void dfs(int x,int y){ 85 p[x][y]=true; 86 if (x==n){ 87 g[now].l=min(g[now].l,y); 88 g[now].r=max(g[now].r,y); 89 } 90 for (int i=0;i<4;i++){ 91 int xx=x+xy[i][0]; 92 int yy=y+xy[i][1]; 93 if (xx>n||xx<1||yy>m||yy<1) continue; 94 if (a[x][y]<=a[xx][yy]) continue; 95 if (!p[xx][yy]) 96 dfs(xx,yy); 97 } 98 }