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 是否存在即可。
效率
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
2.CF987D
题意: 给一个无向图,边权为1,每个点有一个权值(<=k),从一个点出发到达其他点就可以拿到这个到达点的权值,问每一个点拿到s个不同的权值需要跑多远。
QwQ这题还是看题解了...
由于 k 较小,就枚举 k 然后把所有权值是k的拉进队列里然后跑bfs就可以得到一个数组 dist[i][x] 表示 第i个点 拿权值为x 需要的最短路。
然后对dist[i]排序取前s个就好了0.0
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
中途停坑了,原因是去颓废了刷noip了。刷的七七八八差不多回来补坑啦~~
3.CF1009D
题意:构造一个n个点,m条边的图,要求联通且边链接的两个点编号互质。
这题看起来好像数据很大,实际上直接暴力枚举,然后判个gcd,如果可以就输出不然就接着枚举。
注意判联通就好了 可以先让1与其他点连n-1条边,这样必然互质且联通。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
4.CF1013D
题意:有一个n*m的矩阵,现在有q个点x,y 如果有三个点能构成一个矩形,那么这个矩形的剩下一个点就可以获得。问最少要再补充多少个点,才可以利用前面的操作获得全部点。
这题抱了ZincSabian的大腿。
这题可以思考一下并查集,对于一个点x,y 就把 行的x与列的y并起来。
然后最后统计有多少个集合。答案就是集合数-1。
为什么这样是正确的,简单理解为,在同一个集合的所有行和所有列的交点就是可以获得的。为什么?
考虑两个点要并在一起必须要同行或同列。这样集合中依旧是一行两列或两行一列,也就是还是两个点。
再加入一个点,那么必然要与集合中的某行或某列相同,这样实际上与同一个矩形的条件一样了。于是就自动拥有了四个点。
而并查集后中的每一个集合,表示该集合的所有行和所有列的交点都已经得到了。
考虑把剩下的集合并起来怎么办,实际上,只要取一个集合中的一个行,与另一个集合中的一个列,也就是自己找到一个这样的点就可以并起来两个集合。
发现集合数-1次合并后,就只剩下一个集合。也就是n*m个点全部拿到。
不得不说,ZincSabian太强了!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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就好了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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]就好了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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大的时候,可以树状数组。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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为底,即原来的位数。所以...
就证明完了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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 于是就构造完了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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的上一个位置在哪,然后直接更新即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
12.CF998D
题意:给定n,问在1 5 10 50中共选择n个数加起来可以得到多少个不同的数(n<=1e9)
看题解++QAQ
结果这是一个打表题QAQ,比赛的时候没想到打表找规律,看了题解发现在n>=12之后这是一个等差数列...
于是n小的dfs,n大的用公差49算一下...(多打表=w=)
具体证明似乎在官方题解有,笨兔子看不懂(哭唧唧
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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]。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
14.CF991D
题意:一个2*n的0X矩阵,0表示可以放,X表示不能,问最多可以塞多少块方块,方块的样子在题目中。
刚开始想状压,结果转移要好多if语句,情况太多了,于是放弃了。
考虑只有2*n 所以可以贪心,对于一列均为0的时候考虑他前面能不能有一个0,来贪心的组成一个方块。如果不行,就只能在后面的地方找。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
15.CF962D
题意:给定n个数,进行以下操作,取出数组中最小的一个x,(x在数组中个数大于等于2) 将最左边的x删掉,最右边的x*2后保留。直到无法进行操作,问最后的数组的样子。
这题一直想着链表维护当前最小和下一个值。但是插入的时候要二分,菜兔不会链表的二分...
于是翻开了官方题解...并没有看懂,于是又去百度QAQ
事实上,完全可以用优先队列来解决。
只是菜兔没有想到,优先队列的优先级多关键字就好了。第一关键字是数,第二关键字是位置。
每次模拟把第一个和第二个拿出来,看一下x一不一样,一样就把第二个拿出来*2再塞回去。不一样就说明这个数最后没得动了,那个a[]保存住等着 被宰 输出
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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 都行)
然后枚举一下判一下就好了...
注意可以把大的放在第一个分组,也可以把小的放在第一个分组,两个情况都枚举一下就好了...
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
17.CF909D
题意:给定一个串,可以进行操作,如果一个字符的相邻字符与该字符不同,就可以删掉他。而且必须删掉。如果有多个字符满足这样的条件,是全部同时删掉的。
一次操作完之后会得到一个新串,如果依旧可以进行操作,就继续,问操作几次会停。
最直观的想法是直接暴力删,但是一个简单的数据就可以卡掉比如
aaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbb
这样的,一次操作只能删掉一次,于是就TLE了,所以思考一下。把一段相同的缩成一个字符于是变成了
XaYb
然后考虑当前做多少操作可以删掉其中的一个字符。
然后重新计算每一段的个数,把空段删了,然后合并相同串。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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*
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
(傻兔子把 1ll* 写成了11l* 然后 wa 了一次 QAQ
题意:给定一个数组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的值
然后握手定理两两匹配就好了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
注意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的奇偶性。
如果是偶 则 答案奇偶性不变,如果是奇 则 答案奇偶性改变
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
当n较大时可以nlogn求原先的逆序对。
21.CF796D
题意:给定一棵树,k个特殊点,问最多可以删多少边,使得最后仍然满足每一个点离最近的特殊点距离‘小于等于d(给定的图已满足)。
刚开始以为是树形dp,但是距离为d不知道怎么处理,于是看题解QAQ
把所有特殊点丢进队列,然后去bfs就好了。
因为边权都是1,所以满足了每一个点都会离特殊点最近然后入队,而这些入队的边就是必须取的。
剩下的边就都没有用了。标记一下就可以。
然后我无限WA5,不知所措,最后信仰开大数组过了...
事实上每一个点只会入队一次,但是我不知道为什么队列的数组必须开大0.0QAQ 很萌比.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
22.CF779D
题意:给定两个串 s t, 然后有一个长度为|s| 的数组,表示按数组顺序删掉 S 相应位置上的字符。问最多可以删到多少个字符,且 t 是删后的 S 串的一个子序列。
二分一下答案,然后暴力On判是不是子序列就好了。
然后注意QAQ strlen()是On的 最好先int m=strlen();不要再循环里用,不然会 T 的很没有面子QAQ
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
23.CF724D
题意:给定m和一个串s,现在可以从s中选出若干个位置,使得对于任何 i i 距离最近的这若干个位置的距离小于m。问选出的这若干个位置上的字符排序后的字典序最小的串是多少。
这题的话QAQ有点难QAQ而且实现令人作呕QAQ
考虑字典序最小,如果这个字符是最大的那个,那么越少越好,如果不是最大的,那要多少有多少(兔土豪不行咩)
所以枚举一下最大的那个字符,先假设全部塞进去能不能满足要求,如果不行,就说明这个字符不是最大的,那我就把全部字符都要了!
否则贪心的选择塞更少的字符来使字典序最小,塞法就是,尽可能到越后面来塞,实在不能再后了就塞进去。(然后就想起来我写作业的样子QAQ)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
24.CF670D1&&D2
题意:给定n种面包,做一个面包要ai个相应的粉,现在每一种面包有bi个相应的粉,还有k个 膜 魔法粉,可以变成任意一种面包粉。问做成的面包中,最少的面包种类最多是多少
D1 直接二分就好了,然后暴力判断一下
D2 还是直接二分然后暴力判断,但是因为ai bi 数据变大了,而我写的是乘法就会爆 ll 于是r最多只能是2*1e9,。然后check一旦不满足就直接return QAQ
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
我开了ullQAQ
25.CF598D
题意:给定一个矩阵,包含“*”和“.” 然后给q个询问 x,y 问从 x,y 到处乱走,最多可以遇到多少个‘*’ *是不能走的,然后多个点遇到同一个‘*’ 答案是也要计数。
直接bfs找一下联通块就好了,然后标记每一个点属于哪一个联通块,联通块中的答案都是一样的...
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
26.CF609D
题意:有m个东西,一个东西的支付方式只有一种,只能美元买,或者只能英镑买,方式已经给定。现在要买k个东西,有s个 奶萌兔币(不资道叫什么的) 币,买东西只用美元或英镑。
现在有 n 天,每一天有两个汇率 ai 表示第 i 天一个美元需要多少个币,bi 表示第 i 天一个英镑需要多少个币。问最少用多少天可以仅用 s 个币买到 k 个物品。
和前面的一道题类似,二分答案。
然后贪心的去判断,对于物品分成两组,一组是用美元的,一组是用英镑的,读入的时候就把他分组,然后对于前 i 天买,贪心的,在汇率最小的那一天买是最优的。
然后看一下两组中最便宜的,哪一个更便宜就买他。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
27.CF898D
题意:有n个时间会响闹钟,如果这个爱睡懒觉的不要脸的家伙在 一个连续的m段中听见 大于或等于 k 次闹钟这个爱睡懒觉的不要脸的家伙 就会醒。问最少删掉多少个闹钟,可以让这个爱睡懒觉的不要脸的家伙一直睡。
这题的话很容易想到一个贪心,就是对于每一个长度为m的段,就把越靠后的那些不能再塞的删掉。
然后我就一直在想怎么维护这样的东西QAQ,刚开始写了二分发现没法解决删除的点。然后看到了一段区间,就想到了队列呀
对于一个 i 如果能塞就塞进去,不然就ans++,然后注意一下出队就好了
改成队列一下就过了QAQ
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
28.CF754D
题意:给 n 个段[l,r] 自己要找到一个最大的段[x,y]满足[x,y]被n个段中的恰好k个段覆盖。
看题解++QAQ
首先贪心的想法,按 l 从小到大排序,然后依次塞。
考虑有k个段的时候,答案是这 k 个段的最小r-最大l 因为是依次塞的,所以最大 l 就是当前的l。
然后考虑一下当有k+1个的时候,要把哪一个砍掉?贪心的想法是,把最小的那个砍掉,这样次小的就可以篡位,答案就会更优。
所以用一个优先队列维护 r 的最小值,然后大于k个的时候弹掉最小的哪一个。
所以注意!最后答案并不是排序后的一段区间。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
29.CF363D
题意:给定n个男孩,m辆单车,有a块钱是共用的,每一个男孩还有自己的私房钱 bi 每一辆车的钱是pi,问最多有多少人可以买到车,一辆车只能买一次,及在这个前提下,总共花的私房钱最少是多少。
这题特别像noip2017的普及初赛题啊QAQ,因为要在买最多车的前提下,所以二分这个最大的车数。贪心的判断是否可行。
考虑二分到车数为x,那么把最大的x个私房钱的男孩找出来,最便宜的x辆车找出来。
然后从小到大的去买就好了。
对于花的私房钱最少,因为就算这个人私房钱足够多买一辆车,但是这个人还是选择要拿共享的钱。
于是就是最便宜的x辆车加起来的钱数减掉共享的钱就好了。
注意可能为负数。要和0取max。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
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就好了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }