我好菜啊
A
题意:
定义p-二进制数为2^k-p,给出n和p,求用最小个数的p-二进制数来表示n
1<=n<=10^9,-1000<=p<=1000
题解:
猜结论,答案不会很大
n可以表示成kp+s的形式,枚举k,判断(n-kp)是否能用k个2的幂构成
画一下图可以发现,如果可以构成,那么满足(n-kp)的位数<=k<=n-kp
(相当于把一颗二叉树上一个点变成两个)
证明答案不会很大:
首先(n-kp)的位数最多为30
①p>=0
显然当k超过30后,如果不满足则之后也不满足
②p<0
那么n-kp显然大于等于k
code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #define fo(a,b,c) for (a=b; a<=c; a++) #define fd(a,b,c) for (a=b; a>=c; a--) using namespace std; long long n,s; int p,i,j,k,l; bool pd(long long t) { long long T=t; int sum=0; while (T) { sum+=T&1; T>>=1; } if (s>=sum && s<=t) return 1; else return 0; } int main() { // freopen("a.in","r",stdin); scanf("%I64d%d",&n,&p); while (!pd(n)) { n-=p; ++s; if (n<0 ||s>100) break; } if (n>=0 && pd(n)) printf("%I64d\n",s); else printf("-1\n"); }
B
题意:
给出n个数和k,求(i,j)的个数(i<j),使得ai*aj=x^k
n<=10^5,2<=k<=100
题解:
能表示成x的k次方,意味着乘积质因数分解后每一位的指数%k=0
也就是(ai中p的指数+aj中p的指数)%k=0,把i或j的指数取负,变成求ai和aj质因数指数相等的(i,j)个数
可以排序搞,根据k讨论一下大质数
code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #define fo(a,b,c) for (a=b; a<=c; a++) #define fd(a,b,c) for (a=b; a>=c; a--) #define min(a,b) (a<b?a:b) using namespace std; int a[200001]; int p[200001][66]; int P[317]; int c[200001]; int N,n,K,i,j,k,l,s,len,ls,s1,s2; long long ans; bool bz; bool cmp(int a,int b) { int i; fo(i,1,65) if (p[a][i]<p[b][i]) return 1; else if (p[a][i]>p[b][i]) return 0; return 0; } bool pd(int t) { int i; fo(i,1,65) if (p[a[t]][i]!=p[a[t+1]][i]) return 0; return 1; } bool Cmp(int x,int y) { return p[x][0]<p[y][0]; } int main() { // freopen("b.in","r",stdin); fo(i,2,316) { k=1; fo(j,2,floor(sqrt(i))) if (!(i%j)) { k=0; break; } if (k) P[++len]=i; } scanf("%d%d",&n,&K); fo(i,1,n) { scanf("%d",&l); fo(j,1,len) if (!(l%P[j])) { while (!(l%P[j])) { l/=P[j]; ++p[i][j]; } p[i][j]%=K; p[i+n][j]=(K-p[i][j])%K; } if (l>1) { p[i][0]=l; p[i+n][0]=l; } } N=n+n; fo(i,1,N) a[i]=i; // fo(i,1,N) // { // fo(j,0,5) // cout<<p[i][j]<<" "; // cout<<endl; // } sort(a+1,a+N+1,cmp); // fo(i,1,N) // cout<<a[i]<<endl; // fo(i,1,N) // { // fo(j,0,5) // cout<<p[a[i]][j]<<" "; // cout<<endl; // } ls=1; fo(i,1,N) if (i==N || !pd(i)) { l=0; fo(j,ls,i) c[++l]=a[j]; sort(c+1,c+l+1,Cmp); if (K>=3) { s1=0;s2=0; fo(j,1,l) if (!p[c[j]][0]) { if (c[j]<=n) ++s1; else ++s2; } // fo(j,1,l) // if (c[j]<=n) // cout<<c[j]<<" "; // else // cout<<-(c[j]-n)<<" "; // cout<<endl; // cout<<" "<<s1*s2<<" "<<s1<<" "<<s2<<endl; ans+=(long long)s1*s2; } else { s1=0;s2=0; fo(j,1,l) { if (j==1 || p[c[j]][0]==p[c[j-1]][0]) { if (c[j]<=n) ++s1; else ++s2; } else { ans+=(long long)s1*s2; s1=0;s2=0; if (c[j]<=n) ++s1; else ++s2; } } ans+=(long long)s1*s2; } ls=i+1; } if (K==2) { fo(i,1,n) { fo(j,1,65) if ((p[i][j]*2%K)) break; if (j>65) --ans; } } else { fo(i,1,n) if (!p[i][0]) { fo(j,1,65) if ((p[i][j]*2%K)) break; if (j>65) --ans; } } printf("%I64d\n",ans/2); }
C
题意:
给出n*m的方格图,每个格子上有514石头或者为空
从(1,1)开始向(n,m)移动(只能向右/下),每次移动会把一行/一列的石头向右/下推一格,不能把石头推出方格图外
求方案数
题解:差点就切了的sb题
一个合法的路径有若干次转折,可以发现每次转折时所转到的方向上的石头都没被推过
所以只需要考虑当前方向往后的石头个数即可转移,用前缀和优化
code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #define fo(a,b,c) for (a=b; a<=c; a++) #define fd(a,b,c) for (a=b; a>=c; a--) #define mod 1000000007 using namespace std; bool a[2002][2002]; int s1[2002][2002]; int s2[2002][2002]; int f[2002][2002][2]; int g[2002][2002][2]; int n,m,i,j,k,l; char ch; int main() { // freopen("c.in","r",stdin); scanf("%d%d",&n,&m); fo(i,1,n) { fo(j,1,m) { ch=getchar(); while (ch!='.' && ch!='R') ch=getchar(); a[i][j]=ch=='R'; } } if (a[n][m]) { printf("0\n"); return 0; } if (n==1 && m==1) { printf("1\n"); return 0; } fd(i,n,1) { fd(j,m,1) { s1[i][j]=s1[i][j+1]+a[i][j]; s2[i][j]=s2[i+1][j]+a[i][j]; } } f[1][1][0]=1; f[1][1][1]=1; fo(i,1,n) { fo(j,1,m) { if (i>1 || j>1) { f[i][j][0]=g[i][j][0]; f[i][j][1]=g[i][j][1]; } fo(k,0,1) if (f[i][j][k]) { if (!k) { g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod; g[i][m-s1[i][j+1]+1][k^1]=(g[i][m-s1[i][j+1]+1][k^1]-f[i][j][k])%mod; } else { g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod; g[n-s2[i+1][j]+1][j][k^1]=(g[n-s2[i+1][j]+1][j][k^1]-f[i][j][k])%mod; } } g[i][j+1][1]=(g[i][j+1][1]+g[i][j][1])%mod; g[i+1][j][0]=(g[i+1][j][0]+g[i][j][0])%mod; } } printf("%d\n",((f[n][m][0]+f[n][m][1])%mod+mod)%mod); }
D
题意:
把一条链进行若干次操作,每次操作选择一个点,把这个点的父亲设为其父亲的父亲
求最小的操作使得能把链变成给出的一棵树,并给出链的初始编号和每次操作的点编号
题解:
很妙的构造题
一开始想把每个点接到兄弟节点中深度最小的点,但是挂了
考虑把树变成链,一次操作实质是把一个点的父亲设为其的一个兄弟节点
由于每次操作树的深度最多+1,所以操作次数的下限为(n-深度)
先找出树上最长链,每次把最长链上的一个点v下移到兄弟u,下移后最长链多了u
每次深度+1,所以这样的操作次数刚好是(n-深度),即为最优
code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #define fo(a,b,c) for (a=b; a<=c; a++) #define fd(a,b,c) for (a=b; a>=c; a--) #define min(a,b) (a<b?a:b) #define max(a,b) (a>b?a:b) using namespace std; int a[100001][2]; int ls[100001]; int fa[100001]; int nx[100001]; int Ans[100001]; bool bz[100001]; int d[100001]; int n,i,j,k,l,len,ans,tot,tot2,mx,mx2,h,t; void New(int x,int y) { ++len; a[len][0]=y; a[len][1]=ls[x]; ls[x]=len; } void dfs(int t,int d) { int i; if (d>mx) mx=d,mx2=t; for (i=ls[t]; i; i=a[i][1]) fa[a[i][0]]=t,dfs(a[i][0],d+1); } int main() { // freopen("d.in","r",stdin); scanf("%d",&n); fo(i,2,n) { scanf("%d",&fa[i]);++fa[i]; New(fa[i],i); } dfs(1,1); while (mx2) { bz[mx2]=1; nx[fa[mx2]]=mx2; mx2=fa[mx2]; } fo(j,1,n) if (bz[j]) { for (i=ls[j]; i; i=a[i][1]) if (!bz[a[i][0]]) d[++t]=a[i][0]; } while (h<t) { ++h; j=nx[fa[d[h]]]; for (i=ls[d[h]]; i; i=a[i][1]) d[++t]=a[i][0]; fa[j]=d[h]; nx[fa[d[h]]]=d[h]; nx[d[h]]=j; Ans[++tot]=j; } for (i=1; i; i=nx[i]) printf("%d ",i-1); printf("\n"); printf("%d\n",tot); fd(i,tot,1) printf("%d ",Ans[i]-1); }
E
题意:
给出一个数k和n个数(都不能整除k),每次可以把两个数合并,合并后的值除k直到不整除为止
求一种把n个数合并成1的方案
n<=16,k<=2000,∑ai<=2000
题解:
O(3^n*2000)的做法显然过不了
可以发现,每种合并的方案最终都可以表示为∑ai*k^bi,其中bi<0
证明每种∑ai*k^bi=1的情况都能对应一种合法方案:
①n=1
那么只有a1=1时才成立
(不存在a1>1的情况)
②n>1
设序列中最小的bi为B,那么必定存在至少两个bi=B
证明:
若只有一个,那么
∑ai*k^bi=1
∑ai*k^(bi-B)=k^(-B)
当bi>B时,乘积必为k的倍数,而右侧也为k的倍数
如果只有一个bi=B,且ai%k≠0,左右在模k意义下不等,实际也必然不等
那么每次把bi=bj=B的ij合并,把新的数的b变为(b+合并后的数除k的次数),合并的数变为f(i+j)即可
可以发现这样仍满足∑ai*k^bi=1,而n-1的所有情况都已经归纳证明了
③n=2
由于n=1比较特殊,所以n=2也要特殊考虑
从n>1的结论得知,b1=b2=B
因为a1*k^B+a2*k^B=1
a1+a2=k^(-B)
所以f(a1+a2)=1,可以变为n=1的情况,即上文所说不存在a1>1
那么dp就很显然了,考虑每次操作加一个a,或者对于所有的a除以k
把一种合法的序列还原时,可以发现其中不存在小数
所以dp时的ai和也必为整数
用bitset优化,反着还原dp,然后正着求出每次操作
还原dp时不需要记录上个状态(因为有bitset),每次枚举一种操作判断原状态是否存在即可
code
#include <algorithm> #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <bitset> #define fo(a,b,c) for (a=b; a<=c; a++) #define fd(a,b,c) for (a=b; a>=c; a--) using namespace std; struct type{ int a,b; } b[17]; int p[17]; int a[17]; int ans[101]; bitset<2001> f[65536]; int n,K,L,i,j,k,l,sum,len,s,tot; bool cmp(type a,type b) { return a.b>b.b; } int main() { // freopen("e.in","r",stdin); p[1]=1; fo(i,2,16) p[i]=p[i-1]<<1; scanf("%d%d",&n,&K);L=(p[n]<<1)-1; fo(i,1,n) scanf("%d",&a[i]),sum+=a[i]; sum/=K; f[0][0]=1; fo(i,1,L) { fo(j,1,n) if (i&p[j]) f[i]|=f[i^p[j]]<<a[j]; fd(j,sum,1) if (f[i][j*K]) f[i][j]=1; } if (!f[L][1]) printf("NO\n"); else { printf("YES\n"); s=L;j=1; while (j) { if (j<=sum && f[s][j*K]) { j*=K; ans[++len]=-1; continue; } fo(i,1,n) if (s&p[i] && j>=a[i] && f[s-p[i]][j-a[i]]) { s-=p[i]; j-=a[i]; ans[++len]=i; break; } } j=0; fo(i,1,len) if (ans[i]==-1) ++j; else b[++tot]={a[ans[i]],-j}; while (tot>1) { sort(b+1,b+tot+1,cmp); printf("%d %d\n",b[tot-1].a,b[tot].a); b[tot-1].a+=b[tot].a; --tot; while (!(b[tot].a%K)) b[tot].a/=K,++b[tot].b; } } }