Codeforces Round #376 Div.2

A:题意:给定一个由字母表组成的首尾相接的环,环上有一个指针,最初指向字母a,每次可以顺时针或逆时针旋转一格,例如a顺时针转到z,逆时针转到b,然后问转出给定字符串最少需要转多少次。

思路:模拟。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 
 8 char s[1000];
 9 
10 int main(){
11     scanf("%s",s+1);
12     int now=0,ans=0;
13     for (int i=1;i<=strlen(s+1);i++){
14         int t=s[i]-'a';
15         ans+=min(abs(t-now),26-abs(t-now));
16         now=t;
17     }
18     printf("%d\n",ans);
19     return 0;
20 }
A

B:题意:一共有n天,然后每天需要买恰好ai个披萨,对于每天披萨的购买只有两种方式,要么一次买两个披萨,要么一次为今天买一个披萨同时为明天也买一个披萨,不能有其余任何的购买方式,且这两种购买方式可以使用无限次数,问是否每天都能恰好买到ai个。

思路:显然对于第i天的披萨,如果ai是偶数,则直接用第一种购买方式,因为这显然合法且对后面不产生影响,若ai是奇数,则应该先用第一种购买方式买到只剩一个,然后再用第二种购买方式,然后ai+1--,如果存在一个ai<0(包括an+1),则说明不合法,否则合法。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 #define maxn 200005
 8 
 9 int n;
10 int a[maxn];
11 
12 int main(){
13     scanf("%d",&n);
14     for (int i=1;i<=n;i++) scanf("%d",&a[i]);
15     for (int i=1;i<=n;i++){
16         if (a[i]<0){puts("NO");return 0;}
17         if (a[i]&1) a[i]=0,a[i+1]--;
18     }
19     if (a[n+1]<0) puts("NO");else puts("YES");
20     return 0;
21 }
B

C:题意:一共n只袜子,m天,k种颜色,然后给出每一只袜子的初始颜色,然后m天给定每一天穿哪双袜子,然后要给最少的袜子染色使得每一天穿的袜子颜色相同。

思路:对于每一天穿的一双袜子看成两个点,然后连边,那么显然对于一条边连接的两个点颜色是要相同的,也就是说属于一个连通块内的所有点的颜色是要相同的,于是对于每一个连通块找出出现次数最多的颜色,然后用size减去它就是该连通块对答案的贡献。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 #define maxn 200005
 8 
 9 int n,m,k,tot,tmp,top,size,ans;
10 int col[maxn],now[maxn],pre[maxn*10],son[maxn*10],cnt[maxn],stack[maxn];
11 bool vis[maxn];
12 
13 void add(int a,int b){
14     son[++tot]=b;
15     pre[tot]=now[a];
16     now[a]=tot;
17 }
18 
19 void link(int a,int b){
20     add(a,b),add(b,a);
21 }
22 
23 void dfs(int x){
24     size++;
25     if (!cnt[col[x]]) stack[++top]=col[x];
26     cnt[col[x]]++,tmp=max(tmp,cnt[col[x]]);
27     for (int p=now[x];p;p=pre[p])
28         if (!vis[son[p]]) vis[son[p]]=1,dfs(son[p]);
29 }
30 
31 int main(){
32     scanf("%d%d%d",&n,&m,&k);
33     for (int i=1;i<=n;i++) scanf("%d",&col[i]);
34     for (int i=1,a,b;i<=m;i++) scanf("%d%d",&a,&b),link(a,b);
35     for (int i=1;i<=n;i++)
36         if (!vis[i]){
37             vis[i]=1;
38             size=0,tmp=0,dfs(i),ans+=size-tmp;
39             for (int j=1;j<=top;j++) cnt[stack[j]]=0;
40             top=0;
41         }
42     printf("%d\n",ans);
43     return 0;
44 }
C

D:题意:一共n个字符串,c种颜色,然后给出每一个字符串的长度(记为li)再给出每一个字符串(注意n<=500000,li<=500000,sigma(li)<=1000000),每一个字符串之间用空格隔开,然后你可以顺时针(也仅可以顺时针)置换,即对于任意一个字符串的任意一个数字,如果顺时针置换一次,将由1变成2,2变成3...c变成1,并且如果置换一次,所有的数字均会按照上述规则进行变换,问转几次恰好能使所有字符串按照字典序排列。、

思路:显然转c次就是一个循环,因此答案一定是[0,c-1]的一个数,或是0,然后因为数据范围的问题是不可能全部存下来然后离线处理的,因此每读入一个字符串就要在线处理,记录前一个字符串(记为pre[])和后一个字符串(记为now[]),然后一定要使后一个字符串比前一个字符串的字典序大,即找到第一个不同字符,然后如果pre[i]>now[i],那么显然一定要让pre[i]转过c,而使now[i]不能转过c,于是此处的转动的范围即[c-pre[i]+1,c-now[i]],否则如果pre[i]<now[i],那么显然要么不让now[i]转过c,要么pre[i]也转过c,那么此处转动的范围即[0,c-now[i]]∪[c-pre[i]+1,c-1],然后每一次都属于范围之内的元素即答案,用线段树维护,或差分一下均可。然后还要注意一个细节,就是如果pre[i]和now[i]前面所有的元素都相同且pre[i]的长度要大于now[i]的长度显然就无解。

PS:好气啊,比赛时这道题写了好久,结果还因为打错一个变量名GG了,然后后面两道大水题。。。。。。。。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<cstring>
 6 using namespace std;
 7 #define maxn 500005
 8 
 9 int n,c,ans;
10 int now[maxn],pre[maxn];
11 bool first=1;
12 
13 struct segment_tree{
14     struct treenode{
15         bool can;
16     }tree[maxn*4];
17     void build(int p,int l,int r){
18         tree[p].can=1;
19         if (l==r) return;
20         int mid=(l+r)>>1;
21         build(p<<1,l,mid);
22         build(p<<1|1,mid+1,r);
23     }
24     void change(int p,int l,int r,int x,int y){
25         if (!tree[p].can) return;
26         if (x<=l && r<=y){
27             tree[p].can=0;
28             return;
29         }
30         int mid=(l+r)>>1;
31         if (x<=mid) change(p<<1,l,mid,x,y);
32         if (y>mid) change(p<<1|1,mid+1,r,x,y);
33     }
34     void query(int p,int l,int r){
35         if (!tree[p].can) return;
36         if (l==r){if (first) ans=l,first=0;return;}
37         int mid=(l+r)>>1;
38         query(p<<1,l,mid),query(p<<1|1,mid+1,r);
39     }
40 }T;
41 
42 int main(){
43     scanf("%d%d",&n,&c);int prelen=0;
44     T.build(1,0,c-1);
45     for (int i=1;i<=n;i++){
46         int nowlen=0;
47         if (i==1){
48             scanf("%d",&nowlen),prelen=nowlen;
49             for (int j=1;j<=nowlen;j++) scanf("%d",&now[j]),pre[j]=now[j];
50             continue;
51         }
52         scanf("%d",&nowlen);
53         for (int j=1;j<=nowlen;j++) scanf("%d",&now[j]);
54         for (int j=1;j<=prelen;j++){
55             if (j>nowlen/*WA=>prelen*/){T.change(1,0,c-1,0,c-1);break;}
56             if (pre[j]!=now[j]){
57                 if (pre[j]>now[j]) T.change(1,0,c-1,0,c-pre[j]),T.change(1,0,c-1,c-now[j]+1,c-1);
58                 else T.change(1,0,c-1,c-now[j]+1,c-pre[j]);
59                 break;
60             }
61         }
62         prelen=nowlen;
63         for (int j=1;j<=nowlen;j++) pre[j]=now[j];
64     }
65     ans=-1;
66     T.query(1,0,c-1);
67     printf("%d\n",ans);
68     return 0;
69 }
D

E:题意:一共n张贴纸,然后每一张上写有一个数字,每次两人从左边选出至少两个数字(至多可以选完),然后将这些贴纸撕去,再贴一张新的贴纸代表所选的数字之和,然后收益就是所选的所有数字之和,问两人均采取最好策略的情况下,先手收益最多能比后手多多少(如果比后手收益少x则输出-x),数字可能为负。

思路:用f[i]表示从i开始玩(也就是前i个已经被缩为一张了)先手与后手的最大差距,然后显然f[i]一定是由某个j转移过来的,因为当前先手一定是将i到j这一段缩成一个数字,然后f[i]=max{sum[j]-f[j]},令f[i]表示真正的先手(后手也是一样,因为都是最优策略),此处的f[j]虽然表示的先手实际上是真正的后手,然后f[j]=j到n中真正后手的收益-j到n中真正先手的收益,因而sum[j]-f[j]=sum[i]+j到n中真正先手的收益-j到n中真正后手的收益,于是这个转移方程就很显然了,然后直接用ans表示sum[j]-f[j]的最大值,然后用ans=max{sum[i]-ans,ans}转移即可。时间复杂度O(n)。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 #define maxn 200005
 8 
 9 int n;
10 int a[maxn];
11 long long sum[maxn];
12 
13 int main(){
14     scanf("%d",&n);
15     for (int i=1;i<=n;i++) scanf("%d",&a[i]);
16     for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];long long ans=sum[n];
17     for (int i=n-1;i>1;i--) ans=max(ans,sum[i]-ans);
18     printf("%lld\n",ans);
19     return 0;
20 }
E

F:题意:一共n个数,选定一个数使得其余所有数均为该数的倍数,然后该数大小不能变,其余数的大小均减小以满足是选定数的倍数,问减小后的序列之和的最大值是多少。

思路:枚举选定哪一个数i,然后显然可以分块,记录i*j到(i+1)*j-1中一共多少个数,这些数显然一定会变成i*j,然后统计答案即可,时间复杂度O(n*log(n)),最坏时间复杂度O(n*sqrt(n))。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 #define maxn 500005
 8 
 9 int n;
10 int a[maxn],f[maxn];
11 long long ans;
12 
13 int main(){
14     scanf("%d",&n);
15     for (int i=1,x;i<=n;i++) scanf("%d",&x),a[x]++;
16     for (int i=200000;i;i--) f[i]=f[i+1]+a[i];
17     for (int i=1;i<=200000;i++)
18         if (a[i]){
19             long long sum=0;
20             for (int j=i;j<=200000;j+=i) sum+=1ll*(f[j]-f[i+j])*j;
21             ans=max(ans,sum);
22         }
23     printf("%lld\n",ans);
24     return 0;
25 }
F

 

posted @ 2016-10-17 09:07  DUXT  阅读(252)  评论(0编辑  收藏  举报