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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

 

转载于:https://www.cnblogs.com/ZXTLFDeAR/p/4831998.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值