D - Dicy Numbers
积性函数、组合数。其实如果数据组数比较少的话,我们很容易yy到一个矩阵快速幂的做法,复杂度为$O(div(N)^3logM) $。数据组数这么多,$M$又这么大,那我们可以猜想下一定含有什么规律,打个表就可以发现,如果固定$M$,$S(N,M)$是关于$N$的积性函数,那么问题就简单很多了,接下来,我们只要求$S(p^c,M)$,其中$p$是质数。这个东西,其实等于$C_{c+M-1}^c$,为什么呢?首先,我们设第$i$个质数的次幂$a_i$,那么,$S(N,M)$其实就等价于求$\prod_{1}^{M}(1+a_i)=N$的方案数,而现在$N=p^c$,那么对于任意的$i$,都有$a_i=p^{x_i}-1$,其中$0\leq x_i \leq c$,那也就是说等价于求$\sum_{i=1}^{M}x_i=c$的方案数,显然答案就是$C_{c+M-1}^c$。对于组合数的计算,注意到虽然$M$很大,但是$c$会很小,由于$C_n^m=\frac{n!}{m!(n-m)!}=\frac{\prod_{j=0}^{m-1}(n-j)}{m!}$,所以可以暴力计算分子分母,然后对分母求逆元即可。最后,为了求出答案,我们只要将$N$进行质因数分解,求出每个质数的次幂,并计算出对应的方案数,最后乘起来就是答案了。
1 #include<iostream> 2 #include<cstdio> 3 #include<vector> 4 const int N=2e6+1; 5 std::vector<int> g[N]; 6 bool vis[N]; 7 typedef long long ll; 8 const ll mod=1e9+7; 9 int f[N]; 10 ll qpow(ll x,ll y) { 11 ll res=1; 12 for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod; 13 return res; 14 } 15 ll C(int n,int m) { 16 if(n<m) return 0; 17 ll up=1,down=1; 18 for(int j=0;j<m;j++) up=up*(n-j)%mod,down=down*(j+1)%mod; 19 return up*qpow(down,mod-2)%mod; 20 } 21 int main() { 22 for(int i=1;i<N;i++) f[i]=i; 23 for(int i=2;i<N;i++) if(!vis[i]) { 24 for(int j=i;j<N;j+=i) { 25 int num=0; 26 while(f[j]%i==0) num++,f[j]/=i; 27 g[j].push_back(num); 28 vis[j]=1; 29 } 30 } 31 int T; 32 scanf("%d",&T); 33 while(T--) { 34 int n,m; 35 scanf("%d%d",&n,&m); 36 ll ans=1; 37 for(int v:g[n]) ans=ans*C(m+v-1,v)%mod; 38 printf("%lld\n",ans); 39 } 40 return 0; 41 }
E - Everyone wants Khaleesi
考虑A先走一步到达点x,首先x要是必胜点,x是必胜点的条件是:如果x不是n的话,那么x出度的点必须有两个必胜点,否则B一定可以割掉通往必胜点的路径。n点是必胜点,从n点往前推,会发现所有点都是必败点,因为是dag。所以除非A可以一步到达点n,否则A必败。
F - Flipping Rectangles
考虑将r1移到和r2有面积交的地方需要多少步,和k比较大小,接下来就是枚举上下方向移动一步,左右方向移动一步即可
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<stack> #include<queue> using namespace std; typedef long long ll; int T; ll k; ll l1, l2, r1, r2, u1, u2, d1,d2; ll x, y, x1, y1; ll cac(ll a, ll b, ll c){ if(a<=b) return 0; return (a-b+c-1)/c; } ll cac1(ll l1, ll r1, ll u1, ll d1){ ll u=min(u1,u2); ll d=max(d1,d2); ll l=max(l1,l2); ll r=min(r1,r2); if(u<=d||l>=r) return 0; return (u-d)*(r-l); } int main(){ scanf("%d",&T); while(T--){ ll p, q; scanf("%lld%lld%lld%lld",&p,&q,&x,&y); l1=p; r1=p+x; d1=q; u1=q+y; scanf("%lld%lld%lld%lld",&p,&q,&x1,&y1); l2=p; r2=p+x1; d2=q; u2=q+y1; scanf("%lld",&k); ll rs=cac(l2,r1,x); ll ls=cac(l1,r2,x); ll us=cac(d2,u1,y); ll ds=cac(d1,u2,y); ll sum=rs+ls+us+ds; if(sum>k){ printf("0\n"); continue; } k-=sum; ll ans=0; if(rs) l1+=rs*x, r1+=rs*x; if(ls) l1-=ls*x, r1-=ls*x; if(us) u1+=us*y, d1+=us*y; if(ds) u1-=ds*y, d1-=ds*y; ans=max(ans,cac1(l1,r1,u1,d1)); k--; if(k>=0){ ans=max(ans,cac1(l1,r1,u1+y,d1+y)), ans=max(ans,cac1(l1,r1,u1-y,d1-y)); ans=max(ans,cac1(l1+x,r1+x,u1,d1)), ans=max(ans,cac1(l1-x,r1-x,u1,d1)); } k--; if(k>=0){ ans=max(ans,cac1(l1+x,r1+x,u1+y,d1+y)), ans=max(ans,cac1(l1+x,r1+x,u1-y,d1-y)); ans=max(ans,cac1(l1-x,r1-x,u1+y,d1+y)), ans=max(ans,cac1(l1-x,r1-x,u1-y,d1-y)); } printf("%lld\n",ans); } return 0; }
G - Gift Pack
数位$ dp $。这个题比较容易想贪心,譬如说我当时在比赛过程中,想贪心的从高位开始取,但是很容易发现,如果枚举该位的$8$种情况,并非只会产生$0$或者$1$,也可能产生$2$,也就是说,会产生进位,这样贪心就不对了。正确的做法是,我们可以数位$dp$,用$dp[i][al][ar][b][c]$表示从最高位到第i位的贴边界的情况为$(al,ar,b,c)$时的最大答案,其中$(al,ar,b,c)$分别指第一个数是否贴下界,第一个数是否贴上界,第二个数是否贴上界以及第三个数是否贴上界,转移的话,只要枚举该位的$8$种情况,判断下是否越界,将所有情况取最大即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 typedef long long ll; 5 typedef std::pair<ll,ll> P; 6 ll dp[66][2][2][2][2]; 7 ll L,R,A,B; 8 ll dfs(int pos,bool al,bool ar,bool b,bool c) { 9 if(pos<0) return 0; 10 if(dp[pos][al][ar][b][c]!=-1) return dp[pos][al][ar][b][c]; 11 int aL=0,aR=1,upb=1,upc=1; 12 if(al) aL=L>>pos&1; 13 if(ar) aR=R>>pos&1; 14 if(b) upb=A>>pos&1; 15 if(c) upc=B>>pos&1; 16 ll &ans=dp[pos][al][ar][b][c]; 17 for(int i=0;i<8;i++) { 18 int na,nb,nc; 19 na=i&1; 20 nb=i>>1&1; 21 nc=i>>2&1; 22 if(na<aL||na>aR) continue; 23 if(nb>upb) continue; 24 if(nc>upc) continue; 25 ll tmp=(na^nb)+(na^nc)+(nb&nc); 26 tmp<<=pos; 27 ans=std::max(ans,tmp+dfs(pos-1,al&&na==aL,ar&&na==aR,b&&nb==upb,c&&nc==upc)); 28 } 29 return ans; 30 } 31 int main() { 32 int n; 33 //printf("%lld\n",1LL<<61); 34 scanf("%d",&n); 35 while(n--) { 36 scanf("%lld%lld%lld%lld",&L,&R,&A,&B); 37 memset(dp,-1,sizeof(dp)); 38 printf("%lld\n",dfs(61,1,1,1,1)); 39 } 40 return 0; 41 }
楼上代码不好看
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; typedef long long ll; int n; ll l, r, a, b, dp[70][2][2][2][2]; int ua[70], ub[70], dl[70], dr[70]; ll dfs(int k, bool w, bool x, bool y, bool z){ if(k==-1) return 0; if(dp[k][w][x][y][z]!=-1) return dp[k][w][x][y][z]; int upa=y?ua[k]:1; int upb=z?ub[k]:1; int upl=w?dl[k]:0; int upr=x?dr[k]:1; ll ans=0; for(int i=0;i<=upa;i++)for(int j=0;j<=upb;j++) for(int p=upl;p<=upr;p++){ ll g=((p^i)+(i&j)+(j^p))*(1LL<<k); ans=max(ans,dfs(k-1,w&&(p==upl),x&&(p==upr),y&&(i==upa),z&&(j==upb))+g); } return dp[k][w][x][y][z]=ans; } int T; void cac(int *a, ll x){ for(int i=0;i<70;i++) a[i]=0; int k=0; while(x){ a[k++]=(x&1); x>>=1; } } int main(){ scanf("%d",&T); while(T--){ memset(dp,-1,sizeof(dp)); scanf("%lld%lld%lld%lld",&l,&r,&a,&b); cac(ua,a); cac(ub,b); cac(dl,l); cac(dr,r); printf("%lld\n",dfs(61,1,1,1,1)); } return 0; }