codeforces 杂题训练

开个新坑吧...上一个坑是dp,补的巨累QwQ,这次先开30题再慢慢干到50吧...

难度估计在  普及~提高  主要是 cf 中的 D之类的题...自认为难度差不多普及~提高的都会拉进来...

废话不多说,开坑!!!

30/30

1.CF988D

这是一场div3的0.0结果难度逼近div2。

题意: 给 n 个数 从中选出一个子集,要求这个子集里面任意两个数之差都为 2的非负整数幂。输出最大子集。

 

这题看了题解QAQ,看到了结论,最大个数不超过 3 个,于是自己证明了一下。

设有 四个数 A B C D 为子集,只要证明不存在这样的子集,就说明是不超过3个的。

假设 A>B>C>D (注意子集不可能有两个数相等,因为2x≠0) 则这个子集有这样的性质。

②-①得 

      

      

      

      

           因为  为偶数 (除 y-x=0之外)     为偶数 (除 z-x=0之外)  而 一个偶数+1 必为奇数,由此 y-x=0 或 z-x=0 

      由于   都 大于0 而  则y-x≠0 否则   为0 不符合。

      由此得出   x=z  x+1=y然后引入 D 

      

      

      只看 A B D同理可得 x=p 只看 B C D 可得 z+1=p  

      于是 x=z+1 而之前求到了 x=z 于是矛盾,所以不可能存在超过个数为3的子集。

所以捏,枚举一下 x 

再枚举一下 B

只要枚举b的时候拿二分判断一下 A C 是否存在即可。

效率 

 1 #define ll long long
 2 #include<cstdio>
 3 #include<algorithm>
 4 
 5 using namespace std;
 6 const int maxn=2*1e5+50;
 7 ll a[maxn];
 8 int n;
 9 inline int read(){
10   char c=getchar();int x=0,f=1;
11   while (c<'0'||c>'9') {
12      if (c='-') f=-1;
13      c=getchar();
14   }
15   while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
16   return x*f;
17 }
18 int find(int l,int r,ll x){
19    while (l<=r){
20      int m=(l+r)>>1;
21      if (a[m]<x) l=m+1;else r=m-1;
22    }
23    if (l<=n&&a[l]==x) return l;else return 0;
24 }
25 int main(){
26    n=read();
27    int ans=1,ansi=1,ansx=0,ansy=0;
28    ll z=0;
29    for (int i=1;i<=n;i++) a[i]=read();
30    sort(a+1,a+n+1);
31    for (int k=0;k<=32;k++){
32       if (k==0) z=1;else z*=2;
33       for (int i=1;i<=n;i++){
34         int x=find(1,i-1,a[i]-z),y=find(i+1,n,a[i]+z),tot=1;
35         if (x) tot++;
36         if (y) tot++;
37         if (tot==3) {
38            printf("3\n%lld %lld %lld",a[x],a[i],a[y]);
39            return 0;
40         }else{
41            if (tot>ans)ans=tot,ansi=i,ansx=x,ansy=y;
42         }
43       }
44    }
45    printf("%d\n",ans);
46    if (ansx) printf("%lld ",a[ansx]);
47    printf("%lld ",a[ansi]);
48    if (ansy) printf("%lld",a[ansy]);
49    return 0;
50 }
CF988D

 

2.CF987D

题意: 给一个无向图,边权为1,每个点有一个权值(<=k),从一个点出发到达其他点就可以拿到这个到达点的权值,问每一个点拿到s个不同的权值需要跑多远。

QwQ这题还是看题解了...

由于 k 较小,就枚举 k 然后把所有权值是k的拉进队列里然后跑bfs就可以得到一个数组 dist[i][x] 表示 第i个点 拿权值为x 需要的最短路。

然后对dist[i]排序取前s个就好了0.0

 1 #define ll long long
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=1e5+50;
 6 int dist[maxn][105],v[maxn],first[maxn],a[maxn],q[maxn],tot=0,n;
 7 struct enode{
 8     int next,y;
 9 }e[maxn*2];
10 inline int read(){
11     char c=getchar();int x=0;
12     while (c<'0'||c>'9') c=getchar();
13     while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
14     return x;
15 }
16 void adde(int x,int y){
17     e[tot].next=first[x];
18     e[tot].y=y;
19     first[x]=tot++;
20 }
21 void bfs(int x){
22     int head=1,tail=0;
23     for (int i=1;i<=n;i++) {
24         dist[i][x]=maxn;
25         v[i]=0;
26         if (a[i]==x) q[++tail]=i,dist[i][x]=0;
27     }
28     while (head<=tail){
29         int now=q[head];
30         for (int i=first[now];i>=0;i=e[i].next){
31             int y=e[i].y;
32             if (dist[now][x]+1<dist[y][x]) {
33                 dist[y][x]=dist[now][x]+1;
34                 if (!v[y]) v[y]=1,q[++tail]=y;
35             }
36         }
37         head++;
38     }
39 }
40 int main(){
41     n=read();
42     int m=read(),k=read(),s=read();
43     for (int i=1;i<=n;i++) a[i]=read(),first[i]=-1;
44     for (int i=1;i<=m;i++) {
45         int x=read(),y=read();
46         adde(x,y);
47         adde(y,x);
48     }
49     for (int w=1;w<=k;w++) bfs(w);
50     for (int i=1;i<=n;i++){
51         sort(dist[i]+1,dist[i]+k+1);
52         ll ans=0;
53         for (int j=1;j<=s;j++) ans+=dist[i][j];
54         printf("%lld ",ans);
55     }
56     return 0;
57 }
CF987D

 

中途停坑了,原因是去颓废了刷noip了。刷的七七八八差不多回来补坑啦~~

 

3.CF1009D

题意:构造一个n个点,m条边的图,要求联通且边链接的两个点编号互质。

这题看起来好像数据很大,实际上直接暴力枚举,然后判个gcd,如果可以就输出不然就接着枚举。

注意判联通就好了 可以先让1与其他点连n-1条边,这样必然互质且联通。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 int gcd(int a,int b){
 7     if (b) return gcd(b,a%b);else return a;
 8 }
 9 int main(){
10     int n,m;
11     scanf("%d%d",&n,&m);
12     if (m<n-1) {
13         printf("Impossible");
14         return 0;
15     } 
16     int x=m;
17     for (int i=1;i<=n;i++){
18         for (int j=i+1;j<=n;j++){
19             if (gcd(i,j)==1) x--;
20             if (x==0) break;
21         }
22         if (x==0) break;
23     }
24     if (x) printf("Impossible\n");else {
25         printf("Possible\n");
26         for (int i=1;i<=n;i++)
27         for (int j=i+1;j<=n;j++){
28             if (gcd(i,j)==1) printf("%d %d\n",i,j),m--;
29             if (m==0) return 0;
30         }
31     } 
32     return 0;
33 } 
CF1009D

 

4.CF1013D

题意:有一个n*m的矩阵,现在有q个点x,y 如果有三个点能构成一个矩形,那么这个矩形的剩下一个点就可以获得。问最少要再补充多少个点,才可以利用前面的操作获得全部点。

这题抱了ZincSabian的大腿。

这题可以思考一下并查集,对于一个点x,y 就把 行的x与列的y并起来。

然后最后统计有多少个集合。答案就是集合数-1。

为什么这样是正确的,简单理解为,在同一个集合的所有行和所有列的交点就是可以获得的。为什么?

考虑两个点要并在一起必须要同行或同列。这样集合中依旧是一行两列或两行一列,也就是还是两个点。

再加入一个点,那么必然要与集合中的某行或某列相同,这样实际上与同一个矩形的条件一样了。于是就自动拥有了四个点。

而并查集后中的每一个集合,表示该集合的所有行和所有列的交点都已经得到了。

考虑把剩下的集合并起来怎么办,实际上,只要取一个集合中的一个行,与另一个集合中的一个列,也就是自己找到一个这样的点就可以并起来两个集合。

发现集合数-1次合并后,就只剩下一个集合。也就是n*m个点全部拿到。

不得不说,ZincSabian太强了!

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<iostream>
 4 #include<cstring>
 5 #define ll long long
 6 #define max(a,b) (a)>(b)?(a):(b)
 7 #define min(a,b) (a)<(b)?(a):(b)
 8 using namespace std;
 9 int father[400500],v[400500];
10 int fa(int x){
11     if (father[x]==x) return x;
12     return father[x]=fa(father[x]);
13 }
14 int main(){
15     int n,m,q;
16     scanf("%d%d%d",&n,&m,&q);
17     for (int i=1;i<=n+m;i++)
18     father[i]=i;
19     for (int i=1;i<=q;i++){
20         int x,y;
21         scanf("%d%d",&x,&y);
22         int fx=fa(x),fy=fa(n+y);
23         if (fx!=fy) father[fx]=fy;
24     }
25     ll ans=0;
26     for (int i=1;i<=n+m;i++)
27     if (!v[fa(i)]) ans++,v[fa(i)]=1;
28     printf("%I64d",ans-1);
29     return 0;
30 }
CF1013D

 

5.CF1011D

题意:一道交互题,要找到一个数。这个数在1 - m(1e9)之间,每次可以查询 x ,他会回答p=1 x比目标数大, 或回答p=-1 x比目标数小 或回答p=0 x就是目标数。但是有一个约束条件,这个回答可能是错误的。有一个长度为n(30)的数组a[] a[i]=0 那么表示第i次回答是错误的,正确的回答应该是这个回答的相反数,a[i]=1 表示 第i次回答正确。当i大于n时会循环。最多60个询问

事实上如果没有约束条件,这是一个十分裸的二分,考虑到1e9 log 后是30 n也是30。

比赛的时候觉得30+30太卡了,尽管是刚刚好但是没往那边想。想了一会才发现最开始30+30就是解法。

先n次询问,询问1 - n 如果回答是0 就可以return了,如果是-1说明第i次询问他说谎,如果是1说明他没说谎。于是a[]出来了

然后在裸二分。

比赛的时候忘记a数组循环于是忘记%n fst了

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<iostream>
 5 #define ll long long
 6 #define max(a,b) a>b?a:b
 7 #define min(a,b) a<b?a:b
 8 
 9 using namespace std;
10 int a[100];
11 int main(){
12     int m,n;
13     scanf("%d%d",&m,&n);
14     for (int i=1;i<=n;i++){
15         printf("%d\n",i);
16         fflush(stdout);
17         int x;
18         scanf("%d",&x);
19         if (x==0) return 0;else
20         if (x==-1) a[i]=0;else a[i]=1;
21     }
22     int l=n+1,r=m;
23     int num=1;
24     while (l<=r) {
25         int m=(l+r)>>1;
26         printf("%d\n",m);
27         fflush(stdout);
28         int x;
29         scanf("%d",&x);
30         if (a[num]==0) x=-x;
31         if (x==0) return 0;else {
32             if (x==1) l=m+1;else r=m-1;
33         }
34         num++;
35         if (num>n)num-=n;
36     }
37     return 0;
38 }
CF1011D

 

6.CF1006E

题意:给定一颗树(n<=2*1e5),根是1。有q个询问,每次给定u k,求以u为根的子数按照dfs序的第k个数是多少。

很裸的dfs序题,直接以1为根求dfs序,因为子树的dfs序都连在一起。所以v[x]表示x在dfs序中的顺序,然后看一下k-1是不是>=num[x],不然就是-1。直接dfs序 v[x]+k-1就好了。

 1 #include<cstdio>
 2 using namespace std;
 3 struct enode{
 4     int next,y;
 5 }e[200500];
 6 int a[200500],tot=0,totn=0;
 7 int first[200500],v[200500],num[200500];
 8 void adde(int x,int y){
 9     e[tot].next=first[x];
10     e[tot].y=y;
11     first[x]=tot++;
12 }
13 void dfs(int x){
14     a[++totn]=x;
15     v[x]=totn;
16     for (int i=first[x];i>=0;i=e[i].next){
17         int y=e[i].y;
18         dfs(y);
19         num[x]+=num[y];
20     }
21 }
22 int main(){
23     int n,q;
24     scanf("%d%d",&n,&q);
25     for (int i=1;i<=n;i++)
26         first[i]=-1,num[i]=1;
27     for (int i=2;i<=n;i++)
28         scanf("%d",&a[i]);
29     for (int i=n;i>=2;i--)
30         adde(a[i],i);
31     dfs(1);
32     for (int i=1;i<=q;i++){
33         int u,k;
34         scanf("%d%d",&u,&k);
35         k--;
36         if (k>=num[u]) printf("-1\n");else 
37         printf("%d\n",a[v[u]+k]); 
38     }
39     return 0;
40 }
CF1006E

 

7.CF1006F

题意:给n*m的矩阵,(n,m<=20,aij<=1e18) 给定k(k<=1e18) 从 (1,1)出发,只能向下走向右走,走到(n,m)且路上数字异或和为k的方案数。

这题应该是div3中比较难的题了...用meet in the middle 思想,写一个折半搜索。

那么dp[i][x]表示到达第 i 行的那个middle点,xor和为x的方案,可以dfs出来。

但是x极大,不可能保存,他们都用map QAQ(素,我看别人代码了)。 思考一下,因为折半了,搜索量大大减少,到达middle点的状态也不会很多。

考虑对x离散,先用一个dfs把全部状态存起来,然后排序。

当要查询的时候在二分x在状态中的位置就好了。

对于终点dfs的时候,ans+=dp[i][x]就好了。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define ll long long
 4 #define max(a,b) ((a)>(b)?(a):(b))
 5 
 6 using namespace std;
 7 const int dx[]={0,1,-1,0};
 8 const int dy[]={1,0,0,-1};
 9 ll k,d[1000050],c[1000050],a[25][25],ans=0;
10 int n,m,tot=0,now=0;
11 int dp[25][1000050];
12 void dfs(int nowx,int nowy,ll num){
13     if (nowx+nowy==n+1) {
14         c[++tot]=num;
15         return;
16     }
17     for (int i=0;i<2;i++) {
18         int x=nowx+dx[i],y=nowy+dy[i];
19         if (x>0&&y>0&&x<=n&&y<=m){
20             if (x+y<=n+1) dfs(x,y,num^a[x][y]);
21         }
22     }
23 }
24 int find(ll x){
25     int l=1,r=now;
26     while (l<=r){
27         int m=(l+r)>>1;
28         if (d[m]<x) l=m+1;else r=m-1;
29     }
30     if (d[l]==x) return l;
31     return -1;
32 }
33 void dfs1(int nowx,int nowy,ll num){
34     if (nowx+nowy==n+1) {
35         int x=find(num);
36         if (x>0) dp[nowx][x]++;
37         return;
38     }
39     for (int i=0;i<2;i++) {
40         int x=nowx+dx[i],y=nowy+dy[i];
41         if (x>0&&y>0&&x<=n&&y<=m){
42             if (x+y<=n+1) dfs1(x,y,num^a[x][y]);
43         }
44     }
45 }
46 void dfs2(int nowx,int nowy,ll num){
47     if (nowx+nowy==n+1) {
48         int x=find(k^num^a[nowx][nowy]);
49         if (x>0) ans+=dp[nowx][x];
50         return;    
51     }
52     for (int i=2;i<4;i++){
53         int x=nowx+dx[i],y=nowy+dy[i];
54         if (x>0&&y>0&&x<=n&&y<=m) 
55             if (x+y>=n+1) dfs2(x,y,num^a[x][y]);
56     }
57 }
58 int main(){
59     scanf("%d%d%lld",&n,&m,&k);
60     for (int i=1;i<=n;i++)
61         for (int j=1;j<=m;j++)
62         scanf("%lld",&a[i][j]);
63     dfs(1,1,a[1][1]);
64     sort(c+1,c+1+tot);
65     c[0]=-1;
66     for (int i=1;i<=tot;i++){
67         if (c[i]!=c[i-1]) now++;
68         d[now]=c[i];
69     }
70     dfs1(1,1,a[1][1]);
71     dfs2(n,m,a[n][m]);
72     printf("%lld",ans);
73 }
CF1006F

 

8.CF996D

题意:给定n和2n个数,相同的数只有两个,问把相同的数放在相邻位置最少需要交换多少次,只能交换相邻的位置。

这题的话,是比较常见的贪心,然后我想了好久证明QAQ

简单贪心一下,最后的序列顺序是原来序列每个数第一个出现的数。

比如 

  3 1 2 3 1 2

最后 的序列会是 33 11 22 即3第一个出现,1第二个出现 2第三个出现。

证明的话:

  假设最左边的数是 l  与 l 相同的数位置在 r

  那么在 l 和 r 之中可能会有一些数,r到末尾会有一些数,而 l 之前不可能有数,因为 l 是第一个数。

  那么对于把 r 移到 l 位置+1的地方,原来 l r之间的数的顺序不会变, r之后的也不会变,但是如果存在一个数 x在l r 中,y与x相同且y在 r 之后,那么移动 r 的话 x与y就接近了 对答案更优

  但是如果把 l 移到 r位置-1的地方,原来 l r之间的数的顺序不会变, r之后的也不会变,但是如果存在一个数 x在l r 中,y与x相同且y在 r 之后,那么移动 l 的话 x与y就更远了,答案不优。

n很小所以直接暴力n^2就好。对于n大的时候,可以树状数组。

 1 #include<cstdio>
 2 using namespace std;
 3 int a[1000],v[1000];
 4 int main(){
 5     int n;
 6     scanf("%d",&n);
 7     for (int i=1;i<=2*n;i++)
 8         scanf("%d",&a[i]);
 9     int ans=0;
10     for (int i=1;i<=2*n;i++)
11     if (!v[a[i]]){
12         int num=0;
13         for (int j=i+1;j<=2*n;j++){
14             if (a[j]==a[i]) break;
15             if (!v[a[j]]) num++;
16         }
17         ans+=num;
18         v[a[i]]=1;
19     }
20     printf("%d",ans);
21     return 0;
22 }
CF996D

 

9.CF992D

题意:给定n和k,n个数,问有多少连续子段满足p/s=m 要整除,p为该子段的乘积,s为该子段的和。

这题悄悄咪咪看了题解。

这个子段中不包含1的个数不会超过60个。(具体证明可以看原题解

或者简单理解为,当p>s*k的时候就可以break掉了。

所以直接暴力枚举起点,往后跳60个不为1的位置。

预处理nxt[i]表示i的下一个不为的位置就好了。

然后QAQ我wa穿了,因为对于p累乘的时候,完全可能爆ll,所以要判断两个数乘起来后会不会爆ll。

可以考虑log函数,如果两个数的log加起来超过18,就爆ll了,具体证明的话...没有,简略证明:

两个数的乘积的最大位数为原来两个位数相加再+1。log是以10为底,即原来的位数。所以...

就证明完了。

 1 #include<cstdio>
 2 #include<cmath>
 3 #include<iostream>
 4 #include<algorithm>
 5 #define ll long long
 6 using namespace std;
 7 ll nxt[300500],a[300500],sum[300500];
 8 int main(){
 9     ll n,m;
10     scanf("%lld%lld",&n,&m);
11     for (int i=1;i<=n;i++){
12         scanf("%lld",&a[i]);
13         sum[i]=sum[i-1]+a[i];
14     }
15 
16     ll ans=0;
17     nxt[n]=n+1;
18     for (int i=n-1;i>0;i--)
19     if (a[i+1]==1) nxt[i]=nxt[i+1];else nxt[i]=i+1;
20     for (int i=1;i<=n;i++){
21         ll s=1;
22         int x=i,num=0;
23         if (a[x]==1) {
24             if (a[x]*m==s) ans++;
25             x=nxt[x]; 
26         }
27         while (x<=n&&num<=60){
28             if (log(s)+log(a[x])>=19) break;else s=s*a[x];
29             num++;
30             if (s<0||s/m>sum[n]) break;
31             if (s%m==0) {
32                 ll y=s/m-(sum[x]-sum[i-1]);
33                 if (y>=0&&y<=sum[nxt[x]-1]-sum[x]) ans++;
34             }
35             x=nxt[x];
36         }
37     }
38     printf("%lld",ans);
39 }
CF992D

 

10.CF1006D

题意: 构造一个n*m的矩阵,使得第 i 行的异或和为a[i],第j列的异或和为b[j],a[],b[]给定。矩阵的数字可以为0.

这题的构造不难,QAQ,但是难想。看题解了QAQ

对于i>=2  j>=2的位置均为0

对于i=1  j>=2的位置为b[j]

对于i>=2  j=1的位置为a[i]

对于i=1 j=1的位置为a[1]^b[2]^...b[m]或b[1]^a[2]^....a[n]

考虑正确性,a ^0=a 所以对于j>=2和i>=2均满足题意。

因为a^a=0

所以当a[1]^a[2]^a[3]^...a[n]!=b[1]^b[2]^...b[m] 时无解。

a[1]^a[2]^a[3]^...a[n]相当于所有元素的异或和。b同理。所以a与b最后要相同。

因为a^b=c a^c=b

考虑 (1,1)位置(设为x)要满足a[1]和b[1]的要求即 x^b[2]^...b[m]=a[1]

得a[1]^b[2]^...b[m]=x 于是就构造完了。

 1 #include<cstdio>
 2 using namespace std;
 3 int a[300],b[300];
 4 int main(){
 5     int n,m;
 6     int x=0,y=0;
 7     scanf("%d%d",&n,&m);
 8     for (int i=1;i<=n;i++){
 9         scanf("%d",&a[i]);
10         x^=a[i];
11     }
12     for (int i=1;i<=m;i++){
13         scanf("%d",&b[i]);
14         y^=b[i];
15     }
16     if (x!=y) printf("NO");else {
17         printf("YES\n%d ",x^a[1]^b[1]);
18         for (int i=2;i<=m;i++)
19         printf("%d ",b[i]);
20         printf("\n");
21         for (int i=2;i<=n;i++){
22             printf("%d ",a[i]);
23             for (int j=2;j<=m;j++)
24             printf("0 ");
25             printf("\n");
26         }
27     }
28     return 0;
29 }
CF1006D

 

11.CF1005D

div 3的一个D

题意:给定一个数字串,可以分成若干段,每一段的数字和如果是3的倍数那么贡献是1,否则是0,问分成若干段的最大贡献。

看题解+1QAQ

有一个这样的性质,如果 Σs[i]%3=Σs[j]%3 那么说明i与j之间可以分一段贡献为1的

考虑dp[i]表示到第 i 的最优解。dp[i]=max(dp[i],dp[j]+1,dp[i-1]) (Σs[i]%3=Σs[j]%3)

这样是n^2的转移,因为dp[]保存的是最优解所以dp[]是一个不下降序列,那么离 i 最近的一个满足条件的 j 就是最优的。

所以last[now]表示前缀和%3后为now的上一个位置在哪,然后直接更新即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 #define max(a,b) ((a)>(b)?(a):(b))
 4 using namespace std;
 5 char s[200500];
 6 int dp[200500],last[5];
 7 int main(){
 8     scanf("%s",s+1);
 9     int n=strlen(s+1),now=0;
10     last[0]=0;
11     last[1]=last[2]=-1;
12     for (int i=1;i<=n;i++){
13         now=(now+s[i]-48)%3;
14         dp[i]=dp[i-1];
15         if (last[now]>=0) dp[i]=max(dp[i],dp[last[now]]+1);
16         last[now]=i; 
17     }
18     printf("%d",dp[n]);
19     return 0;
20 } 
CF1005D

 

12.CF998D

题意:给定n,问在1 5 10 50中共选择n个数加起来可以得到多少个不同的数(n<=1e9)

看题解++QAQ

结果这是一个打表题QAQ,比赛的时候没想到打表找规律,看了题解发现在n>=12之后这是一个等差数列...

于是n小的dfs,n大的用公差49算一下...(多打表=w=)

具体证明似乎在官方题解有,笨兔子看不懂(哭唧唧

 1 #include<cstdio>
 2 #include<map>
 3 #define ll long long
 4 using namespace std;
 5 const int a[]={50,10,5,1};
 6 map<long long,int>mp;
 7 ll ans=0;
 8 int n;
 9 void dfs(int dep,int last,int sum){
10     if (dep==n){
11         if (!mp[sum]) mp[sum]++,ans++;
12         return;
13     }
14     for (int i=last;i<4;i++)
15     dfs(dep+1,i,sum+a[i]);
16 }
17 int main(){
18     scanf("%d",&n);
19     if (n<=12) dfs(0,0,0);else ans=1ll*(n-12)*49+341;
20     printf("%lld",ans);
21 }
CF998D

 

13.CF1017D

题意:给定m个长度为n的01串,每一个位置(1<=i<=n)上都有一个权值。有q个询问,给定长度为n的01串s和k。设一个函数f(s,t)表示s串与t串相同位置的权值和。

如f("00","10") 有一个地方相同,在第二个位置即i=2所以f("00","10")=w[2] 现在问s与m中的串的f函数小于k的个数.(数据自看)

比赛剩下10+min干出来了...

预处理的比较多,而且要分着预处理,不然会tle。

先预处理cost[i]表示 i 的权值,0表示有,1表示没有。

对于读入的串,不同的最多2^12个,所以那个num[i]表示 i 的个数

接着因为异或是相同则0不同则1。利用这个性质可以预处理ans[i][j] 表示 i 与给定的m个串中异或后的权值为 j的个数。

然后对ans做一个前缀和。

接着答案就是 ans[s][k]。

 1 #include<cstdio>
 2 using namespace std;
 3 int cost[4200],w[15],num[4200];
 4 int ans[4200][150];
 5 int main(){
 6     int n,m,q;
 7     scanf("%d%d%d",&n,&m,&q);
 8     for (int i=1;i<=n;i++)
 9     scanf("%d",&w[i]);
10     for (int i=0;i< 1<<n;i++){
11         for (int j=0;j<n;j++)
12         cost[i]+=((i&(1<< j))==0?w[n-j]:0); 
13     }
14     for (int i=1;i<=m;i++){
15         char s[15];
16         scanf("%s",s);
17         int x=0;
18         for (int j=0;j<n;j++)
19         x=x*2+s[j]-48;
20         num[x]++;
21     }
22     for (int i=0;i< 1 << n;i++)
23     if (num[i]) {
24         for (int j=0;j< 1 <<n;j++)
25         if (cost[i^j]<=100) 
26             ans[j][cost[i^j]]+=num[i];
27     }
28     for (int i=0;i< 1<< n;i++){
29         for (int j=1;j<=100;j++)
30         ans[i][j]+=ans[i][j-1];
31     }
32     
33     for (int i=1;i<=q;i++){    
34         char s[15];
35         int k,x=0;
36         scanf("%s%d",s,&k);
37         for (int j=0;j<n;j++)
38         x=x*2+s[j]-48;
39         printf("%d\n",ans[x][k]);
40     }
41     return 0;
42 } 
CF1017D

 

14.CF991D

题意:一个2*n的0X矩阵,0表示可以放,X表示不能,问最多可以塞多少块方块,方块的样子在题目中。

刚开始想状压,结果转移要好多if语句,情况太多了,于是放弃了。

考虑只有2*n 所以可以贪心,对于一列均为0的时候考虑他前面能不能有一个0,来贪心的组成一个方块。如果不行,就只能在后面的地方找。

 1 #include<cstdio>
 2 #include<cstring>
 3 #define max(a,b) ((a)>(b)?(a):(b)) 
 4 using namespace std;
 5 char a[5][120];
 6 int dp[120][5];
 7 int main(){
 8     scanf("%s%s",a[1]+1,a[2]+1);    
 9     int n=strlen(a[1]+1),ans=0;
10     for (int i=1;i<=n;i++)
11     if (a[1][i]=='0'&&a[2][i]=='0'){
12         for (int j=1;j<=2;j++)
13         if (a[j][i-1]=='0'){
14             a[j][i-1]='X';
15             a[1][i]=a[2][i]='X';
16             ans++;
17             break;
18         }
19         if (a[1][i]=='0'&&a[2][i]=='0'){
20             for (int j=1;j<=2;j++)
21             if (a[j][i+1]=='0'){
22                 a[j][i+1]='X';
23                 a[1][i]=a[2][i]='X';
24                 ans++;
25                 break;
26             } 
27         } 
28     }
29     printf("%d",ans);
30     return 0;
31 }
CF991D

 

15.CF962D

题意:给定n个数,进行以下操作,取出数组中最小的一个x,(x在数组中个数大于等于2) 将最左边的x删掉,最右边的x*2后保留。直到无法进行操作,问最后的数组的样子。

这题一直想着链表维护当前最小和下一个值。但是插入的时候要二分,菜兔不会链表的二分...

于是翻开了官方题解...并没有看懂,于是又去百度QAQ

事实上,完全可以用优先队列来解决。

只是菜兔没有想到,优先队列的优先级多关键字就好了。第一关键字是数,第二关键字是位置。

每次模拟把第一个和第二个拿出来,看一下x一不一样,一样就把第二个拿出来*2再塞回去。不一样就说明这个数最后没得动了,那个a[]保存住等着 被宰 输出

 1 #include<cstdio>
 2 #include<queue>
 3 #define ll long long
 4 using namespace std;
 5 struct numnode{
 6     ll id,x;
 7     operator < (const numnode &a) const {
 8         if (a.x!=x) return a.x<x;
 9         return a.id<id;
10     }
11 }; 
12 ll a[200500];
13 priority_queue<numnode>q;
14 int main(){
15     int n;
16     scanf("%d",&n);
17     for (int i=1;i<=n;i++){
18         numnode k;
19         scanf("%lld",&k.x);
20         k.id=i;
21         q.push(k);
22     }
23     int num=0;
24     while (!q.empty()){
25         numnode first=q.top();
26         q.pop();
27         if (q.empty()) {
28             a[first.id]=first.x;
29             num++;
30             break;
31         }
32         numnode second=q.top();
33         if (first.x==second.x) {
34             second.x*=2;
35             q.pop();
36             q.push(second);
37         }else a[first.id]=first.x,num++;
38     }
39     printf("%d\n",num);
40     for (int i=1;i<=n;i++)
41     if (a[i]) printf("%lld ",a[i]);
42     return 0;
43 } 
CF962D

 

16.CF967D

题意:给定n个数和 k1,k2 问能不能分成两组 A B  使得 k1/(|A|)<=min(A[i])  k2/(|B|)<=min(B[i])  |A| 表示A中数的个数。 |A|+|B|可以不为n。

这题的话,先排序,然后把式子化简一下

k1/min(A[i])<=|A| 于是对于每一个A[i] 能算出要多少个比A[I]大(或等于)的数才能满足要求

同理 k2也一样 于是预处理了 num[i][1] num[i][2] 表示 k1(k2) 下需要多少个比A[i]大(或等于)的数

然后预处理一下 f[i][1] 表示 i 之后是否存在 一个 j 满足  j+num[j][1]<=n 如果有f[i][1]=j (任意一个 j 都行)

然后枚举一下判一下就好了...

注意可以把大的放在第一个分组,也可以把小的放在第一个分组,两个情况都枚举一下就好了...

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 struct numnode{
 5     int x,id;
 6 }a[300500];
 7 int f[300500][3],num[300500][3];
 8 bool cmp(numnode a,numnode b){
 9     return a.x<b.x;
10 }
11 int main(){
12     int n,k1,k2;
13     scanf("%d%d%d",&n,&k1,&k2);
14     for (int i=1;i<=n;i++)
15     scanf("%d",&a[i].x),a[i].id=i;;
16     sort(a+1,a+1+n,cmp);
17     for (int i=1;i<=n;i++){
18         num[i][1]=k1/a[i].x+(k1%a[i].x==0?0:1);
19         num[i][2]=k2/a[i].x+(k2%a[i].x==0?0:1);
20     }
21     for (int i=n;i>0;i--){
22         if (i+num[i][1]-1<=n) f[i][1]=i;else f[i][1]=f[i+1][1];
23         if (i+num[i][2]-1<=n) f[i][2]=i;else f[i][2]=f[i+1][2];
24     }
25     for (int i=1;i<=n;i++){
26         if (i+num[i][1]<=n&&f[i+num[i][1]][2]) {
27             printf("Yes\n%d %d\n",num[i][1],n-f[i+num[i][1]][2]+1);
28             for (int j=i;j<i+num[i][1];j++)
29             printf("%d ",a[j].id);
30             printf("\n");
31             for (int j=f[i+num[i][1]][2];j<=n;j++)
32             printf("%d ",a[j].id);
33             printf("\n");
34             return 0;
35         }        
36         if (i+num[i][2]<=n&&f[i+num[i][2]][1]) {
37             printf("Yes\n%d %d\n",n-f[i+num[i][2]][1]+1,num[i][2]);
38             for (int j=f[i+num[i][2]][1];j<=n;j++)
39             printf("%d ",a[j].id);
40             printf("\n");
41             for (int j=i;j<i+num[i][2];j++)
42             printf("%d ",a[j].id);
43             printf("\n");
44             return 0;
45         }
46     }
47     printf("No");
48 }
CF967D

 

17.CF909D

题意:给定一个串,可以进行操作,如果一个字符的相邻字符与该字符不同,就可以删掉他。而且必须删掉。如果有多个字符满足这样的条件,是全部同时删掉的。

一次操作完之后会得到一个新串,如果依旧可以进行操作,就继续,问操作几次会停。

最直观的想法是直接暴力删,但是一个简单的数据就可以卡掉比如

aaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbb

这样的,一次操作只能删掉一次,于是就TLE了,所以思考一下。把一段相同的缩成一个字符于是变成了

XaYb

然后考虑当前做多少操作可以删掉其中的一个字符。

然后重新计算每一段的个数,把空段删了,然后合并相同串。

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 char s[1000500],t[1000500],c[1000500];
 5 int a[1000500],b[1000500];
 6 int main(){
 7     scanf("%s",s+1);
 8     int n=strlen(s+1),now=1,tot=0;
 9     for (int i=2;i<=n;i++)
10     if (s[i]!=s[i-1]) a[++tot]=now,t[tot]=s[i-1],now=1;else now++;
11     a[++tot]=now;t[tot]=s[n];
12     int ans=0;
13     while (tot>1){
14         int mn=a[1];
15         if (mn>a[tot]) mn=a[tot];
16         for (int i=2;i<tot;i++)
17         if (mn>a[i]/2) mn=a[i]/2;
18         if (mn==0) mn=1;
19         for (int i=1;i<tot;i++)
20         a[i]-=mn,a[i+1]-=mn;
21         int z=0;
22         for (int i=1;i<=tot;i++)
23         if (a[i]>0) {
24             if (!z||t[i]!=c[z]) b[++z]=a[i],c[z]=t[i];else
25                 b[z]+=a[i];    
26         }
27         tot=z;
28         for (int i=1;i<=tot;i++)
29         a[i]=b[i],t[i]=c[i];
30         ans+=mn;
31     }
32     printf("%d",ans);
33 }
CF909D

 

18.CF922D

题意:给定n个串,把n个串按某个顺序拼接起来,问最后拼起来的串最多可以有多少个子序列为 'sh'

看到某个顺序就可以想到贪心,常见的题有noip的国王游戏

对于两个串拼接起来答案有

a在b前 a.s*b.h+a.ans+b.ans

a在b后 b.s*a.h+a.ans+b.ans

其中a.s表示 a 中 s 个数 a.h 表示 a 中 h 个数 a.ans 表示a中 sh 的个数

设 a在b前更优则

a.s*b.h+a.ans+b.ans>b.s*a.h+a.ans+b.ans

即 a.s*b.h>b.s*a.h

所以按这个条件sort一下,然后算一下答案即可,注意1ll*

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ll long long
 5 using namespace std;
 6 char s[100500];
 7 struct numnode {
 8     int s,h;    
 9 }a[100500];
10 int sum[100500];
11 bool cmp(numnode a,numnode b){
12     return 1ll*a.s*b.h>1ll*b.s*a.h;
13 }
14 int main(){
15     int n;
16     scanf("%d",&n);
17     ll ans=0;
18     for (int i=1;i<=n;i++){
19         scanf("%s",s);
20         int len=strlen(s);
21         sum[len]=0;
22         for (int j=len-1;j>=0;j--)
23         sum[j]=sum[j+1]+(s[j]=='h'?1:0);
24         for (int j=0;j<len;j++)
25         if (s[j]=='s') ans+=sum[j],a[i].s++;
26         a[i].h=sum[0];
27     }
28     sort(a+1,a+1+n,cmp);
29     sum[n+1]=0;
30     for (int i=n;i>0;i--)
31     sum[i]=sum[i+1]+a[i].h;
32     for (int i=1;i<=n;i++)
33     ans+=1ll*a[i].s*sum[i+1];
34     printf("%lld",ans);
35     return 0;
36 }
CF992D

(傻兔子把 1ll* 写成了11l* 然后 wa 了一次 QAQ

 

19.AtCoder abc105D

题意:给定一个数组a[]和m,问有多少对(l,r) 满足 sum[r]-sum[l-1] 是m的倍数。

塞一道AtCoder的

这个是比较简单的一场abc了

(sum[r]-sum[l-1])%m=0

sum[r]%m=sum[l-1]%m

于是求一下前缀和,用个map存一下sum[i]%m的值

然后握手定理两两匹配就好了

 1 #include<cstdio>
 2 #include<map>
 3 #define ll long long
 4 using namespace std;
 5 map<int,int>mp;
 6 ll sum[100500];
 7 int main(){
 8     ll n,m;
 9     scanf("%lld%lld",&n,&m);
10     for (int i=1;i<=n;i++){
11         ll x;
12         scanf("%lld",&x);
13         sum[i]=sum[i-1]+x;
14         sum[i]%=m;
15         mp[sum[i]]++;
16     }
17     ll ans=mp[0];
18     if (mp[0]>1) ans+=1ll*mp[0]*(mp[0]-1)/2;
19     mp[0]=0;
20     for (int i=1;i<=n;i++)
21     if (mp[sum[i]]) ans+=1ll*mp[sum[i]]*(mp[sum[i]]-1)/2,mp[sum[i]]=0;
22     printf("%lld",ans);
23     return 0;
24 }
AtCoder abc105D

注意1ll*

 

20.CF911D

题意:给定n个数,有q个操作,每次操作给定l r 表示把 l r 反转 如[1,2,3] 变为 [3,2,1] 问每次反转后数组的逆序对个数是奇数还是偶数。

这题很思维啊QAQ

首先要知道原先逆序对个数,数据小 n^2 求就可以。

然后考虑对于反转 [l,r]   对于 l 左边的数 它右边的比它小的个数不变,所以对逆序对个数无影响。

同理 r 右边的对逆序对个数无影响。

设原来答案为ans , [l,r] 中的逆序对个数为x 那么反转后的答案应为 

ans=ans-x+(r-l+1)*(r-l)/2-x

即减去原先的逆序对再加上原先的顺序对。

ans=ans-(r-l+1)*(r-l)/2-2x

2x比为偶数,而 ans-偶数 后的奇偶性不变。

则考虑(r-l+1)*(r-l)/2的奇偶性。

如果是偶 则 答案奇偶性不变,如果是奇 则 答案奇偶性改变

 1 #include<cstdio>
 2 using namespace std;
 3 int a[3050];
 4 int main(){
 5     int n;
 6     scanf("%d",&n);
 7     for (int i=1;i<=n;i++)
 8     scanf("%d",&a[i]);
 9     int ans=0;
10     for (int i=1;i<=n;i++)
11         for (int j=i+1;j<=n;j++)
12             if (a[i]>a[j]) ans++;
13     ans%=2;
14     int q;
15     scanf("%d",&q);
16     for (int i=1;i<=q;i++){
17         int x,y;
18         scanf("%d%d",&x,&y);
19         if (((y-x+1)*(y-x)/2)%2) ans=1-ans;
20         if (ans) printf("odd\n");else printf("even\n");
21     }
22 }
CF991D

当n较大时可以nlogn求原先的逆序对。

 

21.CF796D

题意:给定一棵树,k个特殊点,问最多可以删多少边,使得最后仍然满足每一个点离最近的特殊点距离‘小于等于d(给定的图已满足)。

刚开始以为是树形dp,但是距离为d不知道怎么处理,于是看题解QAQ

把所有特殊点丢进队列,然后去bfs就好了。

因为边权都是1,所以满足了每一个点都会离特殊点最近然后入队,而这些入队的边就是必须取的。

剩下的边就都没有用了。标记一下就可以。

然后我无限WA5,不知所措,最后信仰开大数组过了...

事实上每一个点只会入队一次,但是我不知道为什么队列的数组必须开大0.0QAQ  很萌比.

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 
 5 struct enode{
 6     int y,nxt;
 7 }e[600500];
 8 int head=1,tail=0,tot=0,num=0;
 9 int first[300500],q[600500],v[300500],a[600500],used[600500];
10 void adde(int x,int y){
11     e[tot].nxt=first[x];
12     e[tot].y=y;
13     first[x]=tot++;
14 }
15 int main(){
16     int n,k,d;
17     scanf("%d%d%d",&n,&k,&d);
18     for (int i=1;i<=n;i++)
19         first[i]=-1;
20     for (int i=1;i<=k;i++){
21         int x;
22         scanf("%d",&x);
23         q[++tail]=x;
24         v[x]=1;
25     }
26     for (int i=1;i<n;i++){
27         int x,y;
28         scanf("%d%d",&x,&y);
29         adde(x,y);
30         adde(y,x);
31     }
32     int ans=0;
33     while (head<=tail){
34         int now=q[head];
35         for (int i=first[now];i>=0;i=e[i].nxt){
36             int y=e[i].y;
37             if (!v[y]){
38                 q[++tail]=y;
39                 v[y]=1;
40                 ans++;
41                 used[i/2+1]=1;
42             }
43         }
44         head++;
45     }
46     printf("%d\n",n-1-ans);
47     for (int i=1;i<n;i++)
48     if (!used[i]) printf("%d ",i);
49     return 0;
50 }
CF796D

 

22.CF779D

题意:给定两个串 s  t, 然后有一个长度为|s| 的数组,表示按数组顺序删掉 S 相应位置上的字符。问最多可以删到多少个字符,且 t 是删后的 S 串的一个子序列。

二分一下答案,然后暴力On判是不是子序列就好了。

然后注意QAQ strlen()是On的 最好先int m=strlen();不要再循环里用,不然会 T 的很没有面子QAQ

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 int n,m;
 5 int v[300500],a[300500];
 6 char s[300500],t[300500];
 7 bool check(int x){
 8     for (int i=1;i<=n;i++)
 9     v[i]=0;
10     for (int i=1;i<=x;i++)
11     v[a[i]]=1;
12     int now=1;
13     for (int i=1;i<=n;i++)
14     if ((!v[i])&&s[i]==t[now]){
15         now++;
16         if (now>m) return 1; 
17     }
18     return 0;
19 }
20 int main(){
21     scanf("%s%s",s+1,t+1);
22     n=strlen(s+1);m=strlen(t+1);
23     for (int i=1;i<=n;i++)
24     scanf("%d",&a[i]);
25     int l=0,r=n;
26     while (l<=r){
27         int m=(l+r)>>1;
28         if (check(m)) l=m+1;else r=m-1;
29     }
30     printf("%d",r);
31 }
CF779D

 

23.CF724D

题意:给定m和一个串s,现在可以从s中选出若干个位置,使得对于任何 i  i 距离最近的这若干个位置的距离小于m。问选出的这若干个位置上的字符排序后的字典序最小的串是多少。

这题的话QAQ有点难QAQ而且实现令人作呕QAQ

考虑字典序最小,如果这个字符是最大的那个,那么越少越好,如果不是最大的,那要多少有多少(土豪不行咩)

所以枚举一下最大的那个字符,先假设全部塞进去能不能满足要求,如果不行,就说明这个字符不是最大的,那我就把全部字符都要了!

否则贪心的选择塞更少的字符来使字典序最小,塞法就是,尽可能到越后面来塞,实在不能再后了就塞进去。(然后就想起来我写作业的样子QAQ)

 1 #include<cstdio>
 2 #include<cstring>
 3 using namespace std;
 4 char s[100500],v[100500];
 5 int main(){
 6     int m;
 7     scanf("%d%s",&m,s+1);
 8     int n=strlen(s+1);
 9     for (int c='a';c<='z';c++) {
10         int flag=1,last=0;
11         for (int i=1;i<=n;i++)
12         if (s[i]==c) v[i]=1;    
13         for (int i=1;i<=n;i++){
14             if (v[i]) last=i;
15             if (i-last>=m) flag=0;
16         }
17         if (!flag) {
18             for (int i=1;i<=n;i++)
19             if (s[i]==c) printf("%c",s[i]);
20         }else {
21             int last=0,lastc=1;
22             for (int i=1;i<=n;i++)
23             if (s[i]==c) v[i]=0;
24             for (int i=1;i<=n;i++){
25                 if (v[i]) last=i;
26                 if (s[i]==c) lastc=i;
27                 if (i-last>=m) {
28                     last=lastc;
29                     printf("%c",c);
30                 } 
31                 
32             }    
33             if (n-last>=m) printf("%c",c);
34             break;
35         }
36     }
37     return 0;
38 }
CF724D

 

24.CF670D1&&D2

题意:给定n种面包,做一个面包要ai个相应的粉,现在每一种面包有bi个相应的粉,还有k个  魔法粉,可以变成任意一种面包粉。问做成的面包中,最少的面包种类最多是多少

D1 直接二分就好了,然后暴力判断一下

D2 还是直接二分然后暴力判断,但是因为ai bi 数据变大了,而我写的是乘法就会爆 ll 于是r最多只能是2*1e9,。然后check一旦不满足就直接return QAQ

 1 #include<cstdio>
 2 #define max(a,b) ((a)>(b)?(a):(b))
 3 #define ll unsigned long long
 4 using namespace std;
 5 ll n,k;
 6 ll a[1000050],b[1000050];
 7 ll l=0,r=2*1e9;
 8 bool check(ll x){
 9     ll need=0;
10     for (int i=1;i<=n;i++)
11     if (b[i]<1ll*x*a[i]) {
12         need+=1ll*a[i]*x-b[i];
13         if (need>k) return 0;
14     }
15     return 1;
16 }
17 int main(){
18     scanf("%llu%llu",&n,&k);
19     for (int i=1;i<=n;i++)
20     scanf("%llu",&a[i]);
21     for (int i=1;i<=n;i++)
22     scanf("%llu",&b[i]);
23     while (l<=r){
24         ll m=(l+r)>>1;
25         if (check(m)) l=m+1;else r=m-1;
26     }
27     printf("%llu",r);
28     return 0;
29 }
CF670D1&&D2

我开了ullQAQ

 

25.CF598D

题意:给定一个矩阵,包含“*”和“.” 然后给q个询问 x,y 问从 x,y 到处乱走,最多可以遇到多少个‘*’ *是不能走的,然后多个点遇到同一个‘*’ 答案是也要计数。

直接bfs找一下联通块就好了,然后标记每一个点属于哪一个联通块,联通块中的答案都是一样的...

 1 #include<cstdio>
 2 using namespace std;
 3 const int dx[]={0,1,0,-1};
 4 const int dy[]={1,0,-1,0};
 5 int fa[1050][1050],qx[2000050],qy[2000050],ans[2000050];
 6 char a[1050][1050];
 7 
 8 int tot=0;
 9 int n,m,q;
10 void bfs(int sx,int sy){
11     int head=1,tail=1;
12     fa[sx][sy]=++tot;
13     qx[1]=sx;
14     qy[1]=sy;
15     while (head<=tail){
16         int nowx=qx[head],nowy=qy[head];
17         for (int i=0;i<4;i++){
18             int x=nowx+dx[i],y=nowy+dy[i];
19             if (a[x][y]=='*') ans[tot]++;else 
20             if (!fa[x][y]) qx[++tail]=x,qy[tail]=y,fa[x][y]=tot;
21         }
22         head++;
23     }
24 }
25 int main(){
26     scanf("%d%d%d",&n,&m,&q);
27     for (int i=1;i<=n;i++)
28         scanf("%s",a[i]+1);
29     for (int i=1;i<=n;i++)
30         for (int j=1;j<=m;j++)
31         if ((!fa[i][j])&&a[i][j]=='.') bfs(i,j);
32     for (int i=1;i<=q;i++){
33         int x,y;
34         scanf("%d%d",&x,&y);
35         printf("%d\n",ans[fa[x][y]]);
36     }
37     return 0;
38 }
CF598D

 

26.CF609D

题意:有m个东西,一个东西的支付方式只有一种,只能美元买,或者只能英镑买,方式已经给定。现在要买k个东西,有s个 奶萌兔币(不资道叫什么的)  币,买东西只用美元或英镑。

现在有 n 天,每一天有两个汇率 ai 表示第 i 天一个美元需要多少个币,bi 表示第 i 天一个英镑需要多少个币。问最少用多少天可以仅用 s 个币买到 k 个物品。

和前面的一道题类似,二分答案。

然后贪心的去判断,对于物品分成两组,一组是用美元的,一组是用英镑的,读入的时候就把他分组,然后对于前 i 天买,贪心的,在汇率最小的那一天买是最优的。 

然后看一下两组中最便宜的,哪一个更便宜就买他。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define ll long long
 4 #define min(a,b) ((a)<(b)?(a):(b))
 5 
 6 using namespace std;
 7 
 8 struct numnode{
 9     int x,id;
10 }x[200500],y[200500];
11 int n,m,k,s;
12 int a[200500],b[200500];
13 int totx=0,toty=0;
14 bool check(int m){
15     int z=k,i=1,j=1;
16     ll use=0;
17     while (z--){
18         if (1ll*a[m]*x[i].x<=1ll*b[m]*y[j].x&&i<=totx) use+=1ll*a[m]*x[i++].x;else 
19             if (j<=toty) use+=1ll*b[m]*y[j++].x;else use+=1ll*a[m]*x[i++].x;
20     }
21     if (use>s) return 1;else return 0;
22 }
23 bool cmp(numnode a,numnode b){
24     return a.x<b.x;
25 }
26 int main(){
27     scanf("%d%d%d%d",&n,&m,&k,&s);
28     for (int i=1;i<=n;i++){
29         int x;
30         scanf("%d",&x);
31         if (i==1) a[i]=x;else a[i]=min(a[i-1],x);
32     }
33     for (int i=1;i<=n;i++){
34         int x;
35         scanf("%d",&x);
36         if (i==1) b[i]=x;else b[i]=min(b[i-1],x);
37     }
38     
39     for (int i=1;i<=m;i++){
40         int t,c;
41         scanf("%d%d",&t,&c);
42         if (t==1) x[++totx].x=c,x[totx].id=i;else 
43             y[++toty].x=c,y[toty].id=i;
44     }
45     sort(x+1,x+1+totx,cmp);
46     sort(y+1,y+1+toty,cmp);
47     int l=1,r=n;
48     while (l<=r){
49         int m=(l+r)>>1;
50         if (check(m)) l=m+1;else r=m-1;
51     }
52     if (l>n) return printf("-1"),0;else {
53         printf("%d\n",l);
54         int z=k,daya=1,dayb=1;
55         for (int i=2;i<=l;i++)
56         if (a[i]<a[i-1]) daya=i;
57         for (int i=2;i<=l;i++)
58         if (b[i]<b[i-1]) dayb=i;
59         int i=1,j=1;
60         while (z--){
61         if (1ll*a[l]*x[i].x<=1ll*b[l]*y[j].x&&i<=totx) printf("%d %d\n",x[i++].id,daya);else 
62             if (j<=toty) printf("%d %d\n",y[j++].id,dayb);else printf("%d %d\n",x[i++].id,daya);
63         }
64     }
65 }
CF609D

 

27.CF898D

题意:有n个时间会响闹钟,如果这个爱睡懒觉的不要脸的家伙在 一个连续的m段中听见 大于或等于 k 次闹钟这个爱睡懒觉的不要脸的家伙 就会醒。问最少删掉多少个闹钟,可以让这个爱睡懒觉的不要脸的家伙一直睡。

这题的话很容易想到一个贪心,就是对于每一个长度为m的段,就把越靠后的那些不能再塞的删掉。

然后我就一直在想怎么维护这样的东西QAQ,刚开始写了二分发现没法解决删除的点。然后看到了一段区间,就想到了队列呀

对于一个 i 如果能塞就塞进去,不然就ans++,然后注意一下出队就好了

改成队列一下就过了QAQ

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 int a[200500],q[200500];
 5 int n,m,k;
 6 int main(){
 7     scanf("%d%d%d",&n,&m,&k);
 8     for (int i=1;i<=n;i++)
 9     scanf("%d",&a[i]);
10     sort(a+1,a+1+n);
11     int ans=0,head=1,tail=0;
12     for (int i=1;i<=n;i++){
13         while (head<=tail&&a[i]-a[q[head]]+1>m) head++;
14         if (tail-head+1+1<k) q[++tail]=i;else ans++;
15     }
16     printf("%d",ans);
17     return 0;
18 }
CF898D

 

28.CF754D

题意:给 n 个段[l,r] 自己要找到一个最大的段[x,y]满足[x,y]被n个段中的恰好k个段覆盖。

看题解++QAQ

首先贪心的想法,按 l 从小到大排序,然后依次塞。

考虑有k个段的时候,答案是这 k 个段的最小r-最大l 因为是依次塞的,所以最大 l 就是当前的l。

然后考虑一下当有k+1个的时候,要把哪一个砍掉?贪心的想法是,把最小的那个砍掉,这样次小的就可以篡位,答案就会更优。

所以用一个优先队列维护 r 的最小值,然后大于k个的时候弹掉最小的哪一个。

所以注意!最后答案并不是排序后的一段区间。

 1 #include<cstdio>
 2 #include<queue>
 3 #include<algorithm>
 4 using namespace std;
 5 struct numnode{
 6     int l,r;
 7     int id;
 8     operator < (const numnode &a) const{
 9         return a.r<r;
10     }
11 }a[300500];
12 priority_queue<numnode>q;
13 bool cmp(numnode a,numnode b){
14     if (a.l!=b.l) return a.l<b.l;
15     return a.r<b.r;
16 }
17 int main(){
18     int n,k;
19     scanf("%d%d",&n,&k);
20     for (int i=1;i<=n;i++)
21         scanf("%d%d",&a[i].l,&a[i].r),a[i].id=i;
22     sort(a+1,a+1+n,cmp);
23     int ans=0,x=k;
24     for (int i=1;i<=n;i++){
25         q.push(a[i]);
26         if (i>=k) {
27             if (q.top().r-a[i].l+1>ans) ans=q.top().r-a[i].l+1;
28             q.pop();
29         }
30     }
31     printf("%d\n",ans);
32     while (!q.empty()) q.pop();
33     for (int i=1;i<=n;i++){
34         q.push(a[i]);
35         if (i>=k) {
36             if (q.top().r-a[i].l+1==ans){
37                 while (!q.empty()) {
38                     printf("%d ",q.top().id);
39                     q.pop();
40                 }
41                 return 0;
42             }
43             q.pop();
44         }
45     }
46     if (ans==0) {
47         for (int i=1;i<=k;i++)
48         printf("%d ",i);
49         return 0; 
50     }
51     return 0;
52 } 
CF754D

 

29.CF363D

题意:给定n个男孩,m辆单车,有a块钱是共用的,每一个男孩还有自己的私房钱 bi 每一辆车的钱是pi,问最多有多少人可以买到车,一辆车只能买一次,及在这个前提下,总共花的私房钱最少是多少。

这题特别像noip2017的普及初赛题啊QAQ,因为要在买最多车的前提下,所以二分这个最大的车数。贪心的判断是否可行。

考虑二分到车数为x,那么把最大的x个私房钱的男孩找出来,最便宜的x辆车找出来。

然后从小到大的去买就好了。

对于花的私房钱最少,因为就算这个人私房钱足够多买一辆车,但是这个人还是选择要拿共享的钱。

于是就是最便宜的x辆车加起来的钱数减掉共享的钱就好了。

注意可能为负数。要和0取max。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define ll long long
 4 using namespace std;
 5 int n,m;
 6 ll a;
 7 int p[100500],b[100500];
 8 bool check(int x){
 9     ll need=0;
10     for (int i=1;i<=x;i++)
11     if (p[i]>b[n-x+i]) need+=p[i]-b[n-x+i];
12     if (need<=a) return 1;else return 0;
13 }
14 int main(){
15     scanf("%d%d%lld",&n,&m,&a);
16     for (int i=1;i<=n;i++)
17     scanf("%d",&b[i]);
18     sort(b+1,b+1+n);
19     for (int i=1;i<=m;i++)
20     scanf("%d",&p[i]);
21     sort(p+1,p+1+m);
22     int l=0,r=n;
23     if (r>m) r=m;
24     while (l<=r){
25         int m=(l+r)>>1;
26         if (check(m)) l=m+1;else r=m-1;
27     }
28     printf("%d",r);
29     ll need=0;
30     for (int i=1;i<=r;i++)
31     need+=p[i];
32     if (need-a<0) printf(" 0");else 
33     printf(" %lld",need-a);
34     return 0;
35 }
CF363D

 

30.CF122D

题意:给定一个长度为 n 的串,进行k次以下的操作:找到一个最小的x 满足s(x)='4'&&s(x+1)='7'  如果 x 是奇数 把s(x)和s(x+1)改成'4' 否则把s(x)和s(x+1)改成'7'。问 k 次后串的样子。k 1e9

先预处理所有满足的位置,然后对于一次操作,可以O(1)找到下一个。这样的效率O(k)

我以为这个k是假的可以直接做,但是事实上有一个循环即447

所以特判这个循环。然后k%2就好了。

 1 #include<cstdio>
 2 using namespace std;
 3 char s[100500];
 4 int next[100500];
 5 int main(){
 6     int n,k;
 7     scanf("%d%d",&n,&k);
 8     scanf("%s",s+1);
 9     int last=0;
10     for (int i=1;i<n;i++)
11     if (s[i]=='4'&&s[i+1]=='7') {
12         next[last]=i;
13         last=i;
14     }
15     next[last]=n+1;
16     int now=next[0];
17     while (now<n&&k){
18         k--;
19         if (now%2) {
20             s[now]=s[now+1]='4';
21             if (now+2<=n&&s[now+2]=='7') {    
22                 next[now+1]=next[now];
23                 now++;
24             }else now=next[now];
25         }else {
26             if (s[now-1]=='4'&&s[now+1]=='7') k%=2;
27             s[now]=s[now+1]='7';
28             if (now>1&&s[now-1]=='4') {            
29                 next[now-1]=next[now];
30                 now--;
31             }else now=next[now];
32         }
33     }
34     printf("%s",s+1);
35     return 0;
36 } 
CF122D

 

posted @ 2018-06-09 16:46  Bunnycxk  阅读(261)  评论(0编辑  收藏  举报