题解:
题目相对其他省难一点
不过弱省省选知识点都这么集中的么。。
4道数学题。。。
1.[HAOI2018]奇怪的背包
这题考场做就gg了。。。
其实我想到了那个性质。。 就是这个一定要是gcd的倍数
但是我傻逼的觉得这个不对。。 因为xi都要>=0
然后就看题解。。
仔细想了一下 这可是模意义下啊??
你要是负数你一直加p答案不是不变的么。。。
然后有了这个性质我们考虑dp
直接$f[i][j]$表示考虑了前i个数,gcd为j这个复杂度比较gg
我们发现j一定是p的因数,所以离散化一下(hash表查找)
设p的因数个数为M
这个复杂度是$nMlogn$的 还是gg。。。
我们再看一看 每次加入一个数实际上是与它取gcd
而gcd一定也是y的因数 所以刚开始本质不同的数只有M种
对于相同的数,不取就一种方案,取就是$(2^k-1)$
然后这样dp就是$M^2logM$
讲道理M的上限是sqrt的。。(不知道差了多少倍 程序要跑太久。。)
但反正是能过了。。
// luogu-judger-enable-o2 // luogu-judger-enable-o2 #include <bits/stdc++.h> #define rint register int #define IL inline #define rep(i,h,t) for (int i=h;i<=t;i++) #define dep(i,t,h) for (int i=t;i>=h;i--) #define ll long long #define me(x) memset(x,0,sizeof(x)) #define mid ((h+t)>>1) using namespace std; const int INF=1e9; namespace IO{ char ss[1<<24],*A=ss,*B=ss; IL char gc() { return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++; } template<class T>void read(T &x) { rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48); while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f; } char sr[1<<24],z[20]; int Z,C=-1; template<class T>void wer(T x) { if (x<0) sr[++C]='-',x=-x; while (z[++Z]=x%10+48,x/=10); while (sr[++C]=z[Z],--Z); } IL void wer1() { sr[++C]=' '; } IL void wer2() { sr[++C]=' '; } template<class T>IL void maxa(T &x,T y) {if (x<y) x=y;} template<class T>IL void mina(T &x,T y) {if (x>y) x=y;} template<class T>IL T MAX(T x,T y) {return x>y?x:y;} template<class T>IL T MIN(T x,T y) {return x<y?x:y;} }; using namespace IO; const int mo=5e6+7; struct Hash{ int H[mo+10000],H1[mo+10000]; IL void push(int x,int z) { int y=x%mo; while (H[y]!=0&&H[y]!=x) ++y; H[y]=x; H1[y]=z; } IL int query(int x) { int y=x%mo; while (H[y]!=0&&H[y]!=x) ++y; return H1[y]; } }H; const int N=1e6+1e5; const int M=3.5e4+100; int a[N],f[N],pp[M]; int g[2][M],now[M],now2[M],o[N]; int gcd(int x,int y) { return (!y)?x:gcd(y,x%y); } const int mo2=1e9+7; IL void js(register int &x,register int y) { x+y>mo2?x=x+y-mo2:x=x+y; } int main() { int n,q,p; read(n); read(q); read(p); rep(i,1,n) read(a[i]); int k=sqrt(p); int cnt=0; rep(i,1,k) if (p%i==0) f[++cnt]=i; k=cnt; dep(i,k,1) if (p!=f[i]*f[i]) f[++cnt]=p/f[i]; rep(i,1,cnt) H.push(f[i],i); rep(i,1,n) pp[H.query(gcd(a[i],p))]++; o[0]=1; rep(i,1,1000000) o[i]=o[i-1]*2%mo2; rep(i,1,1000000) o[i]=((o[i]-1)%mo2+mo2)%mo2; g[1][cnt]=1; rep(i,1,cnt) { int *g1=g[i%2],*g2=g[(i+1)%2]; memset(g[(i+1)%2],0,sizeof(g[0][0])*(cnt+10)); if (pp[i]) rep(j,1,cnt) if (g1[j]) { js(g2[H.query(gcd(f[i],f[j]))],(1ll*g1[j]*o[pp[i]])%mo2); } rep(j,1,cnt) js(g2[j],g1[j]); } memcpy(now,g[(cnt+1)%2],sizeof(g[(cnt+1)%2])); rep(i,1,cnt) { int w=p/f[i]; int k=sqrt(w); rep(j,1,k) if (w%j==0) { int t=H.query(f[i]*j); if (t) js(now2[t],now[i]); if (j*j!=w) { t=H.query(f[i]*w/j); if (t) js(now2[t],now[i]); } } } rep(i,1,q) { int x; read(x); x=H.query(gcd(x,p)); wer(now2[x]); wer2(); } fwrite(sr,1,C+1,stdout); return 0; }
2.[HAOI2018]反色游戏
这个感觉思路其实不难 但好像并没有往这上面想
第一感觉就是这个可以化成xor然后线性基高斯消元
最后线性基中为0的点就可以任取 答案为2^k 注意还要判断合不合法 不合法直接为0
然后发现就算没有多次询问就一组也过不了???
然后我就觉得应该是有个黑科技我没学过。。。。
先说一下部分分。。
50分暴力高斯消元就可以了 ($n^4$)
60分需要bitset优化($n^4/32$)
// luogu-judger-enable-o2 #include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for (int i=h;i<=t;i++) #define dep(i,t,h) for (int i=t;i>=h;i--) #define ll long long #define me(x) memset(x,0,sizeof(x)) const int N=210; const int mo=1e9+7; int T,n,m; struct re{ int a,b; }; vector<re> ve[N]; char c[N]; bitset<N> B[N],now; int ans[N],f[N],g[2000]; bool cmp(int x,int y) { return x>y; } int main() { ios::sync_with_stdio(false); g[0]=1; rep(i,1,1000) g[i]=(g[i-1]*2)%mo; cin>>T; rep(ttt,1,T) { cin>>n>>m; rep(i,1,n) ve[i].clear(); rep(i,1,m) { int x,y; cin>>x>>y; ve[x].push_back((re){i,y}); ve[y].push_back((re){i,x}); } cin>>c; rep(i,0,n-1) f[i+1]=c[i]-'0'; rep(i,0,n) { bool tt=1; int c3=0; rep(j,1,n) for (int k=0;k<ve[j].size();k++) if (j==i||ve[j][k].b==i) c3++; c3/=2; rep(j,1,m) B[j]=0,ans[j]=0; rep(j,1,n) if (j!=i) { now=0; int ans2=f[j]; for (int k=0;k<ve[j].size();k++) if (ve[j][k].b!=i) now[ve[j][k].a]=1; dep(k,m,1) if (now[k]) { if (B[k]!=0) { now^=B[k],ans2^=ans[k]; if (now==0) break; } else { B[k]=now; ans[k]=ans2; break; } } if (now==0&&ans2) tt=0; } int cnt=0; rep(j,1,m) if (B[j]==0) cnt++; cnt-=c3; if (!tt) cout<<0<<" "; else cout<<g[cnt]<<" "; } cout<<endl; } return 0; }
70分我不知道是什么东西。。。 我会线段树分治做到($n^3logn/32$) 但这个常数明显大而且肯定过不了70啊。。
然后考虑一下正解
发现这题要用线性基其实是假的
一条边只能连两个节点啊。。。
所以我们考虑合并
如果合并之前两点已经有关系说明这条边就任意了
于是简化一下要求的就是2^(n-m-联通块个数)
另外怎么判断合不合法的充要条件是 如果黑点数为奇数就不合法
来简单证明一下
必要性:改变的颜色数是边数*2的 黑点为奇数显然不行
充分性:如果改变了偶数个,每次我们就任取两个扩展一条他们之前的连边 如果重复就把边取反就可以了
于是我们要支持删一个点求联通块个数和有无奇数个黑点
分类讨论了
3.
由于后缀数组和后缀自动机有点忘了。。以后再补
day2为啥只有两题。。
4.[HAOI2018]苹果树
我用了跟题解不太一样的方法 当然题解更加简单。。
令$f(i)$表示i个点排成子树的方案数 (我刚开始并没有想到这个是n!)
$f(i)=sum C(n,i)*f(i)*f(n-i)$
令$g(i)$表示i个点排成子树到根的路径和
$g(i)=sum$
令$h(i)$表示i个点排成子树内部路径和
$h(i)=sum$
题解的方法也比较常见
考虑每条边的贡献
#include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for (int i=h;i<=t;i++) #define dep(i,t,h) for (int i=t;i>=h;i--) #define ll long long #define me(x) memset(x,0,sizeof(x)) namespace IO{ char ss[1<<24],*A=ss,*B=ss; IL char gc() { return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++; } template<class T>void read(T &x) { rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48); while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f; } char sr[1<<24],z[20]; int Z,CC=-1; template<class T>void wer(T x) { if (x<0) sr[++CC]='-',x=-x; while (z[++Z]=x%10+48,x/=10); while (sr[++CC]=z[Z],--Z); } IL void wer1() { sr[++CC]=' '; } IL void wer2() { sr[++CC]=' '; } template<class T>IL void mina(T &x,T y) { if (x>y) x=y;} template<class T>IL void maxa(T &x,T y) { if (x<y) x=y;} template<class T>IL T MIN(T x,T y){return x<y?x:y;} template<class T>IL T MAX(T x,T y){return x>y?x:y;} }; using namespace IO; const int N=2050; int nn,mo; int C[N][N],f[N],g[N],h[N]; IL void js(int &x,int y) { x=x+y>mo?x+y-mo:x+y; } int main() { read(nn); read(mo); int m=2000; rep(i,1,m) { C[i][0]=C[i][i]=1; rep(j,1,i-1) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo; } f[0]=f[1]=1; rep(i,2,nn) { int n=i-1; rep(j,0,n) js(f[i],1ll*f[j]*f[n-j]%mo*C[n][j]%mo); rep(j,0,n) js(g[i],2ll*g[j]%mo*C[n][j]%mo*f[n-j]%mo); js(g[i],1ll*f[i]*n%mo); rep(j,0,n) js(h[i],2ll*h[j]%mo*C[n][j]%mo*f[n-j]%mo); rep(j,0,n) js(h[i],(2ll*j%mo*(n-j)%mo*f[j]%mo*f[n-j]%mo+2ll*g[j]%mo*(n-j)%mo*f[n-j]%mo)%mo*C[n][j]%mo); js(h[i],g[i]); } // rep(i,1,nn) cout<<f[i]<<" "<<g[i]<<" "<<h[i]<<endl; cout<<h[nn]<<endl; return 0; }
5.[HAOI2018]染色
这个题型比较经典
首先我们考虑取出k个正好有s的
$C(n,s)*C(n-s,s)*...*C(n-k,s) $然后这个可以化简一下
然后因为这个已经保证了顺序 颜色只需要$C(m,k)$就行了(不是$A(s,k)$)
令$F(n,m)$表示n个点用m种颜色染,没有为k的方案数
这个比较显然可以容斥来做 式子太长不想写。。。
列出式子再带进去
我的ntt方法好像和网上不太一样
$ans=f(i)*sum{g(j)*h(i+j)}$
然后我们变换一下 令$p(i)=h(t-i)$
这样就是卷积形式了
写代码的时候正推预处理了1e7的inv
然后发现这个跑的贼慢。。直接exgcd算比这个快多了
有一个常数更小的方法是先计算$(n!)$的逆元然后再倒推回去
这样就不需要除法和取模了
好久没打ntt。。
ntt的n 应该是算出结果的最高项次数
刚开始没算0的情况
这种题目基本最简单的数据对了就对了
所以可以自己造比较好手模的数据
代码:
#include <bits/stdc++.h> using namespace std; #define rint register int #define IL inline #define rep(i,h,t) for(int i=h;i<=t;i++) #define dep(i,t,h) for(int i=t;i>=h;i--) #define ll long long #define me(x) memset(x,0,sizeof(x)) namespace IO{ char ss[1<<24],*A=ss,*B=ss; IL char gc() { return A==B&&(B=(A=ss)+fread(ss,1,1<<24,stdin),A==B)?EOF:*A++; } template<class T> void read(T &x) { rint f=1,c; while (c=gc(),c<48||c>57) if (c=='-') f=-1; x=(c^48); while (c=gc(),c>47&&c<58) x=(x<<3)+(x<<1)+(c^48); x*=f; } char sr[1<<24],z[20]; int Z,C=-1; template<class T>void wer(T x) { if (x<0) sr[++C]='-',x=-x; while (z[++Z]=x%10+48,x/=10); while (sr[++C]=z[Z],--Z); } IL void wer1() { sr[++C]=' '; } IL void wer2() { sr[++C]=' '; } template<class T>IL void maxa(T &x,T y) {if (x<y) x=y;} template<class T>IL void mina(T &x,T y) {if (x>y) x=y;} template<class T>IL T MAX(T x,T y){return x>y?x:y;} template<class T>IL T MIN(T x,T y){return x<y?x:y;} }; using namespace IO; const int mo=1004535809; const int N=1e6+10; const int M=1e6; const int N1=1e7+10; const int N2=1e7; int ww[N1],inv[N1],jc[N1],jc2[N1]; int cj[N],nicj[N]; int f[N],g[N],h[N]; void gcd(int x,int y,int &a,int &b) { if (!y) { a=1; b=0; return; } gcd(y,x%y,b,a); b-=a*(x/y); } int ksm(int x,int y) { if (y==0) return(1); if (y==1) return(x); int k=ksm(x,y/2); k=(1ll*k*k)%mo; if (y&1) k=(1ll*k*x)%mo; return k; } IL void js(register int &x,register int y) { x=x+y>=mo?x+y-mo:x+y; } int l,n1,r[N],w[N],x[N],y[N],G=3; void ntt(int *a,int o) { //n n1别写错 for (int i=0;i<n1;i++) if (i>r[i]) swap(a[i],a[r[i]]); for (int i=1;i<n1;i*=2) { int wn=ksm(G,(mo-1)/(i*2)); w[0]=1; rep(j,1,i-1) w[j]=(1ll*w[j-1]*wn)%mo; for(int j=0;j<n1;j+=(i*2)) { rint *x=a+j,*y=a+i+j; for (rint k=0;k<i;k++) { int t=1ll*w[k]*y[k]%mo; y[k]=x[k]-t<0?x[k]-t+mo:x[k]-t; x[k]=x[k]+t>mo?x[k]+t-mo:x[k]+t; } } } if (o==-1) { reverse(&a[1],&a[n1]); for (int i=0,inv=ksm(n1,mo-2);i<n1;i++) a[i]=1ll*a[i]*inv%mo; } } int t; void query() { l=0; for (n1=1;n1<=t*2;n1<<=1) l++; for (int i=0;i<n1;i++) r[i]=(r[i/2]/2)|((i&1)<<(l-1)); ntt(f,1); ntt(g,1); for (int i=0;i<n1;i++) f[i]=(1ll*f[i]*g[i])%mo; ntt(f,-1); } int main() { freopen("1.in","r",stdin); freopen("1.out","w",stdout); int n,m,s; read(n); read(m); read(s); rep(i,0,m) read(ww[i]); inv[1]=1; rep(i,2,N2) inv[i]=(-1ll*(mo/i)*inv[mo%i]%mo+mo)%mo; jc2[0]=1; jc[0]=1; cj[0]=1; nicj[0]=1; rep(i,1,N2) jc[i]=1ll*jc[i-1]*i%mo,jc2[i]=1ll*jc2[i-1]*inv[i]%mo; int x,y; gcd(jc[s],mo,x,y); x=(x+mo)%mo; rep(i,1,M) { cj[i]=1ll*cj[i-1]*jc[s]%mo; nicj[i]=1ll*nicj[i-1]*x%mo; } t=MIN(m,n/s); rep(i,0,t) if (i%2==0) f[i]=1ll*nicj[i]*jc2[i]%mo; else f[i]=(-1ll*nicj[i]*jc2[i]%mo+mo)%mo; rep(i,0,t) g[i]=1ll*ksm((m-i),n-i*s)*jc2[n-i*s]%mo*jc2[m-i]%mo; reverse(g,g+t+1); query(); rep(i,0,t) h[i]=(f[t-i]+mo)%mo; /* rep(i,0,t) rep(j,i,t) js(h[j-i],1ll*f[i]*g[j]%mo); */ int ans=0; rep(i,0,t) { int t1=1ll*ww[i]*jc[n]%mo*nicj[i]%mo*jc[m]%mo*jc2[i]%mo; t1=1ll*t1*h[i]%mo; js(ans,t1); } cout<<ans<<endl; return 0; }