zoukankan      html  css  js  c++  java
  • 六校联考2019CSP 选做

    更新中...

    以下所有标注“片段”的参考代码,都缺少一个“头”,“头”里包括了一些我的习惯性的简写(如typedef long long ll;)。没有“头”并不影响理解代码。如果想要完整代码,可以去本博客公告栏的链接里,把这个“头”复制到“代码片段”前面。

    day1-流量

    题目链接

    根据“每个点处流入的流量之和等于流出的流量之和”,当且仅当一个点仅有一条未知流量的边时,可以推断得知这条边的状态。当然,这种推断是会产生连锁反应的。例如,初始时,只有几个点满足“仅有一条未知流量的边”,然后我们把这些边推断出来后,又会产生一些新的点满足这一条件。

    进一步,发现,如果还未确定状态的边(构成的子图)中存在一个,则环上的边的流量,永远无法被推断出来。同时发现,若不存在环,则一定可以推断出所有边的流量(按上一段描述的过程,从叶子到根进行“连锁反应”即可)。因此,我们不询问的边的集合,就是原图的最大生成森林

    求这个最大生成森林即可。注意,(w_ileq 0)的边一定要询问(因为问了没有坏处),也就是不被加入最大生成森林中。最后,设最大生成森林的边权和为(s),则答案就是((sum_{i=1}^{m}w_i)-s)

    时间复杂度(O(mlog m+nlog n))

    参考代码(片段):

    const int MAXN=2e5,MAXM=5e5;
    int n,m,fa[MAXN+5],sz[MAXN+5];
    int get_fa(int x){return (x==fa[x])?x:(fa[x]=get_fa(fa[x]));}
    struct ed{int u,v,w;}e[MAXM+5];
    bool cmp(ed x,ed y){return x.w>y.w;}
    int main() {
    	cin>>n>>m;ll sum=0;
    	for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1;
    	for(int i=1;i<=m;++i){cin>>e[i].u>>e[i].v>>e[i].w;sum+=e[i].w;}
    	sort(e+1,e+m+1,cmp);
    	for(int i=1;i<=m;++i){
    		if(e[i].w<=0)break;
    		int u=get_fa(e[i].u),v=get_fa(e[i].v);
    		if(u!=v){
    			sum-=e[i].w;
    			if(sz[u]>sz[v])swap(u,v);
    			fa[u]=v;
    			sz[v]+=sz[u];
    		}
    	}
    	cout<<sum<<endl;
    	return 0;
    }
    

    day1-个人练习生

    题目链接

    考虑二分答案( ext{mid})。可以对每个点(u),求出一个( ext{lim}[u])表示点(u)最迟什么时候必须出发。

    先令( ext{lim}[u]= ext{mid}-a[u]- ext{dep}[u])( ext{dep}[u])是点(u)到根路径上的边数)。但这还不完全,因为我们还要考虑到“祖先必须在后代之后出发”这一要求。于是再做一遍dfs,让每个点的( ext{lim}[u]),对( ext{lim}[fa(u)]-1)(min)。这样就求出了真正的( ext{lim}[u]):每个点的最迟出发时间。

    然后可以做一个贪心:对( ext{lim}[1dots n])排序,让值小的先出发。发现当前答案( ext{mid})合法,当且仅当排好序后(forall iin[1,n]: ext{lim}[i]geq i-1)

    因为要二分答案和排序,时间复杂度(O(ncdot log ncdot log a)),只能拿到(80)分。

    发现其实不用二分答案。因为( ext{mid})的大小,并不会影响( ext{lim}[i])相对大小关系。所以可以先假设( ext{mid}=0),求出( ext{lim}[1dots n])并排好序。然后答案就等于:(max_{i=1}^{n}{i-1- ext{lim}[i]})

    时间复杂度(O(nlog n))(log)来自排序。

    参考代码(片段):

    const int MAXN=3e5,MAXA=1e9;
    int n,a[MAXN+5];
    vector<int>G[MAXN+5];
    int fa[MAXN+5],dep[MAXN+5];
    void dfs_prep(int u){
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa[u]) continue;
    		fa[v]=u;
    		dep[v]=dep[u]+1;
    		dfs_prep(v);
    	}
    }
    int lim[MAXN+5];
    void dfs_calc_lim(int u){
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa[u]) continue;
    		lim[v]=min(lim[v],lim[u]-1);
    		dfs_calc_lim(v);
    	}
    }
    int main() {
    	cin>>n;
    	for(int i=1;i<=n;++i)
    		cin>>a[i];
    	for(int i=1;i<n;++i){
    		int u,v; cin>>u>>v;
    		G[u].pb(v); G[v].pb(u);
    	}
    	dfs_prep(1);
    	for(int i=1;i<=n;++i)
    		lim[i]=-a[i]-dep[i];
    	dfs_calc_lim(1);
    	sort(lim+1,lim+n+1);
    	int ans=0;
    	for(int i=1;i<=n;++i){
    		ans=max(ans,i-1-lim[i]);
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    day1-假摔

    题目链接

    考虑一对可能成为最优方案的((A,B)),要满足什么条件。

    • 如果存在一个(x),满足(A<x<B),且(a_xgeq a_A),则((x,B,C))是一组更优的合法解。因此:(forall xin(A,B):a_x<a_A)
    • 如果存在一个(x),满足(A<x<B),且(a_xgeq a_B),则((A,x,C))是一组更优的合法解,因此:(forall xin (A,B):a_x<a_B)

    因此,合法的((A,B)),总共只有(O(n))对:也就是每个位置(A)和它后面第一个(a_Bgeq a_A)(B);以及每个位置(B),和它前面第一个(a_Ageq a_B)(A)。可以用单调栈求出。

    然后考虑回答询问。

    离线。从大到小枚举左端点,每次加入一个(A)(也就是加入若干对((A,B)))。

    对每个位置(i),假设我们维护出一个(f[i])表示以(i)作为(C)时的最优答案。假设当前枚举到的左端点为(l),当前要处理的询问右端点为(r),则我们只需要查询(f)数组在([l,r])区间内的最大值即可。

    考虑加入一个(A)(f)数组的影响。前面说过,这也就是加入以它为(A)的所有((A,B))。因为((A,B))总数只有(O(n))对,所以可以暴力依次加入。对一对((A,B))而言,它影响到的(C),必须满足(B-Aleq C-B),移项得(Cgeq 2B-A)。因此我们对所有(igeq 2B-A),令(f[i])(a_A+a_B+a_i)(max)即可。

    用线段树维护。修改操作有些特殊。做法是,预处理出每个区间,初始时(a_i)的最大值,记为( ext{maxa}(l,r))。这样区间修改时,令( ext{maxf}(l,r):=max( ext{maxf}(l,r), ext{maxa}(l,r)+a_A+a_B))即可。区间查询就是直接求( ext{maxf})的最大值。

    时间复杂度(O((n+q)log n))

    考虑这道题的本质。一开始是一个((A,B,C))三元问题,很不好回答。我们是怎么拆解它的?用离线去掉了一元(A),通过观察(A,B)的关系去掉了一元(B),然后剩下的一元(C)用数据结构维护它。

    参考代码(片段):

    const int MAXN=5e5;
    pii sta[MAXN+5];
    int n,m,a[MAXN+5],top;
    ll ans[MAXN+5];
    struct Q{int l,r,id;}q[MAXN+5];
    bool cmp(Q x,Q y){return x.l>y.l;}
    struct SegmentTree{
    	//区间取max,求区间max
    	ll val[MAXN*4+5],tag[MAXN*4+5],mx[MAXN*4+5];
    	void build(int p,int l,int r){
    		if(l==r){val[p]=mx[p]=a[l];return;}
    		int mid=(l+r)>>1;
    		build(p<<1,l,mid);
    		build(p<<1|1,mid+1,r);
    		val[p]=mx[p]=max(val[p<<1],val[p<<1|1]);
    	}
    	void push_down(int p){
    		if(tag[p]){
    			val[p<<1]=max(val[p<<1],mx[p<<1]+tag[p]);
    			val[p<<1|1]=max(val[p<<1|1],mx[p<<1|1]+tag[p]);
    			tag[p<<1]=max(tag[p<<1],tag[p]);
    			tag[p<<1|1]=max(tag[p<<1|1],tag[p]);
    			tag[p]=0;
    		}
    	}
    	void modify(int p,int l,int r,int ql,int qr,ll v){
    		if(ql<=l && qr>=r){val[p]=max(val[p],mx[p]+v);tag[p]=max(tag[p],v);return;}
    		push_down(p);
    		int mid=(l+r)>>1;
    		if(ql<=mid)modify(p<<1,l,mid,ql,qr,v);
    		if(qr>mid)modify(p<<1|1,mid+1,r,ql,qr,v);
    		val[p]=max(val[p<<1],val[p<<1|1]);
    	}
    	ll query(int p,int l,int r,int ql,int qr){
    		if(ql<=l && qr>=r)return val[p];
    		push_down(p);
    		int mid=(l+r)>>1;ll res=0;
    		if(ql<=mid)res=query(p<<1,l,mid,ql,qr);
    		if(qr>mid)res=max(res,query(p<<1|1,mid+1,r,ql,qr));
    		return res;
    	}
    }T;
    int main() {
    	cin>>n;
    	for(int i=1;i<=n;++i)cin>>a[i];
    	cin>>m;
    	for(int i=1;i<=m;++i){cin>>q[i].l>>q[i].r;q[i].id=i;}
    	sort(q+1,q+m+1,cmp);
    	T.build(1,1,n);
    	sta[top=1]=mk(a[n-1],n-1);
    	int j=n-2;
    	for(int i=1;i<=m;){
    		for(;j>=q[i].l;--j){
    			while(top && sta[top].fst<=a[j]){
    				if(sta[top].scd*2-j<=n)T.modify(1,1,n,sta[top].scd*2-j,n,a[j]+sta[top].fst);
    				--top;
    			}
    			if(top&&sta[top].scd*2-j<=n)T.modify(1,1,n,sta[top].scd*2-j,n,a[j]+sta[top].fst);
    			sta[++top]=mk(a[j],j);
    		}
    		int ii=i;
    		for(;ii<=m&&q[ii].l==q[i].l;++ii)ans[q[ii].id]=T.query(1,1,n,q[ii].l+2,q[ii].r);
    		i=ii;
    	}
    	for(int i=1;i<=m;++i)cout<<ans[i]<<endl;
    	return 0;
    }
    

    day2-文体两开花

    题目链接

    对每个点,维护一个(f(u))表示它所有儿子的(val)的异或和。再维护一个(g(u)),表示所有儿子的(f)的异或和。

    这样,修改和查询都可以(O(1))维护。

    时间复杂度(O(n+q))

    参考代码(片段):

    const int MAXN=1e6,MOD=1e9+7;
    int n,m,val[MAXN+5],fa[MAXN+5],f[MAXN+5],g[MAXN+5];
    vector<int>G[MAXN+5];
    void dfs(int u){
    	f[u]=val[u];
    	for(int i=0;i<(int)G[u].size();++i){
    		int v=G[u][i];
    		if(v==fa[u])continue;
    		fa[v]=u;
    		dfs(v);
    		f[u]^=val[v];
    		g[u]^=f[v];
    	}
    }
    int main() {
    	cin>>n>>m;
    	for(int i=1;i<=n;++i)cin>>val[i];
    	for(int i=1,u,v;i<n;++i){cin>>u>>v;G[u].pb(v),G[v].pb(u);}
    	dfs(1);f[0]=val[1];
    	int ans=0;
    	for(int i=1;i<=m;++i){
    		int u,x;cin>>u>>v;
    		if(fa[fa[u]])g[fa[fa[u]]]^=f[fa[u]];
    		g[fa[u]]^=val[u];g[fa[u]]^=x;
    		f[fa[u]]^=val[u];f[fa[u]]^=x;
    		f[u]^=val[u];f[u]^=x;
    		if(fa[fa[u]])g[fa[fa[u]]]^=f[fa[u]];
    		ans=(ans+(ll)(g[u]^f[fa[u]]^val[fa[fa[u]]])%MOD*i%MOD*i%MOD)%MOD;
    		val[u]=x;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    day2-国际影星

    题目链接

    补集转化,求不合法的方案,也就是点(1)能到达的点与点(2)能到达的点交为空。

    因为(n)很小。考虑状压DP。设(dp_{1/2}[S]),表示从点(1)(2)出发,只考虑点集(S)及其内部的边的情况下,能到达(S)里任意一个点,此时给点集(S)内部的边定向的方案数。

    更明确地讲,一个点集内部的边,就是指两个端点都在点集里的边。我们不难对每个点集,预处理出它内部的边的数量,记为(E[S])

    转移时,还是考虑补集转化,用总的方案减去不合法的方案。总的方案就是(2^{E[S]}),即任意连边。考虑计算不合法的方案数。枚举一个真子集(Tsubsetneq S),计算恰好只能到达点集(T)的方案数(显然(1)(2)必须在(T)中)。此时(Ssetminus T)内部可以任意连边,而(T)(Ssetminus T)之间的边必须都连向(T)。所以可以得到转移:

    [dp_{x}[S]=2^{E[S]}-sum_{Tsubsetneq S,xin T}dp_{x}[T] imes 2^{E[Ssetminus T]} ]

    DP完成后,考虑统计答案。枚举两个互不相交的集合(s_1,s_2)表示最终(1)能到达的点集和(2)能到达的点集。设(r)表示其他点的集合,即(r={1,dots ,n}setminus s_1setminus s_2)。则(r)内部的的边可以任意连,而(r)(s_1,s_2)之间的边必须全部连向(s_1)(s_2)(s_1,s_2)之间不能有连边。

    [ ext{ans}=2^{m}-sum_{substack{s1,s2in{1,dots,n}\s_1cap s_2=emptyset\ ext{EdgeBetween}(s_1,s_2)=emptyset\1in s_1,2in s_2}}dp_1[s_1] imes dp_2[s_2] imes 2^{E[r]} ]

    主要涉及到子集枚举,用这个技巧:for(int i=s; i; i=((i-1)&s)) 可以实现不重不漏的枚举。

    时间复杂度(O(3^n))

    参考代码(片段):

    const int M=(1<<16),MOD=1e9+7;
    inline int mod(int x){return x<MOD?(x<0?x+MOD:x):x-MOD;}
    int n,m,G[16],pw[300],cnt[M],f[M],g[M];
    int main() {
    	pw[0]=1;for(int i=1;i<300;++i)pw[i]=mod(pw[i-1]<<1);
    	n=read();m=read();
    	for(int i=1;i<=m;++i){int u=read()-1,v=read()-1;G[u]|=(1<<v);G[v]|=(1<<u);}
    	for(int s=1;s<(1<<n);++s){
    		for(int i=0;i<n;++i)if(s&(1<<i))cnt[s]+=__builtin_popcount(G[i]&s);
    		cnt[s]>>=1;
    	}
    	f[1]=1;
    	for(int s=5;s<(1<<n);s+=4){
    		f[s]=pw[cnt[s]];
    		int c=s^1;
    		for(int ss=(c-1)&s;;ss=(ss-1)&s){
    			f[s]=mod(f[s]-(ll)f[ss^1]*pw[cnt[c^ss]]%MOD);
    			if(!ss)break;
    		}
    	}
    	g[2]=1;
    	for(int s=6;s<(1<<n);s+=4){
    		g[s]=pw[cnt[s]];
    		int c=s^2;
    		for(int ss=(c-1)&s;;ss=(ss-1)&s){
    			g[s]=mod(g[s]-(ll)g[ss^2]*pw[cnt[c^ss]]%MOD);
    			if(!ss)break;
    		}
    	}
    	int ans=pw[m];
    	for(int s1=1;s1<(1<<n);s1+=4){
    		int c=s1;
    		for(int i=0;i<n;++i)if(s1&(1<<i))c|=G[i];
    		if(c&2)continue;
    		c=(((1<<n)-1)&(~c))^2;
    		for(int s2=c;;s2=(s2-1)&c){
    			ans=mod(ans-(ll)f[s1]*g[s2^2]%MOD*pw[cnt[((1<<n)-1)^s1^(s2^2)]]%MOD);
    			if(!s2)break;
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

    day2-零糖麦片

    题目链接

    相当于有一个(01)序列,每次可以让连续的一段异或(1)。初始时有(k)个位置上是(1)。问最少多少次操作后,可以把整个序列变成(0)

    对原序列做(operatorname{xor})意义下的差分。具体来说,设原序列为(a[0dots n]),并且钦定(a[0]=0)。那么差分序列就是(b[1dots n])满足(b[i]=a[i]operatorname{xor}a[i-1])

    那么“让一段区间异或(1)”,就变成了让差分序列上两个点异或(1)。并且原序列全是(0),就等价于差分序列全是(0)(充分必要)。

    要把差分序列上这些(1)消去,考虑将它们两两匹配。对于一对匹配了的(1),要将它们消去:

    • 如果他们距离为一个奇质数,则需要(1)次操作。
    • 如果他们距离为一个偶数,则需要(2)次操作。这是根据哥德巴赫猜想:任何一个大于等于(6)的偶数一定能拆成两个奇质数的和,并且它被证明在(10^7)以内是正确的。
    • 如果他们距离为一个非质数的奇数,则需要(3)次操作:拆成一个奇质数和一个偶数。

    于是根据贪心,我们肯定希望距离为奇质数的匹配尽可能多。又发现这样的匹配,两个点一定一个是奇数一个是偶数,所以可以根据奇偶性建二分图,求出最大匹配。

    剩下的点,尽量和同奇偶性的匹配即可。

    时间复杂度(O( ext{flow}(k,k^2)))

    const int N=1e7,INF=1e9;
    int n,a[1005],p[1000000],cntp,pos0[2005],pos1[2005],ct0,ct1;
    bool v[N+1];
    void sieve(){
    	v[1]=1;
    	for(int i=2;i<=N;++i){
    		if(!v[i])p[++cntp]=i;
    		for(int j=1;j<=cntp&&i*p[j]<=N;++j){
    			v[i*p[j]]=1;
    			if(i%p[j]==0)break;
    		}
    	}
    }
    namespace Flowinginging{
    	const int MAXN=2010;
    	struct EDGE{int nxt,to,w;}edge[MAXN*MAXN];
    	int head[MAXN],tot;
    	void add_edge(int u,int v,int w){
    		edge[++tot].nxt=head[u];edge[tot].to=v;edge[tot].w=w;head[u]=tot;
    		edge[++tot].nxt=head[v];edge[tot].to=u;edge[tot].w=0;head[v]=tot;
    	}
    	int d[MAXN],cur[MAXN];
    	bool bfs(int s,int t){
    		memset(d,0,sizeof(d));d[s]=1;
    		queue<int>q;q.push(s);
    		while(!q.empty()){
    			int u=q.front();q.pop();
    			for(int i=head[u];i;i=edge[i].nxt){
    				int v=edge[i].to;
    				if(!d[v]&&edge[i].w){
    					d[v]=d[u]+1;
    					if(v==t)return 1;
    					q.push(v);
    				}
    			}
    		}
    		return 0;
    	}
    	int dfs(int u,int t,int flow){
    		if(u==t)return flow;
    		int rest=flow;
    		for(int &i=cur[u];i&&rest;i=edge[i].nxt){
    			int v=edge[i].to;
    			if(d[v]==d[u]+1 && edge[i].w){
    				int k=dfs(v,t,min(rest,edge[i].w));
    				if(!k)d[v]=0;
    				else{
    					edge[i].w-=k;
    					edge[i^1].w+=k;
    					rest-=k;
    					//return flow-rest;
    				}
    			}
    		}
    		return flow-rest;
    	}
    	int maxflow(int s,int t){
    		int ans=0,tmp;
    		while(bfs(s,t)){
    			for(int i=1;i<=t;++i)cur[i]=head[i];
    			while(tmp=dfs(s,t,INF))ans+=tmp;
    		}
    		return ans;
    	}
    };
    using namespace Flowinginging;
    int main() {
    	sieve();
    	cin>>n;
    	for(int i=1;i<=n;++i)cin>>a[i];
    	sort(a+1,a+n+1);
    	for(int i=1;i<=n;){
    		int j=i+1;
    		while(j<=n && a[j]==a[j-1]+1)++j;
    		--j;
    		if(a[i]&1)pos1[++ct1]=a[i];else pos0[++ct0]=a[i];
    		if((a[j]+1)&1)pos1[++ct1]=a[j]+1;else pos0[++ct0]=a[j]+1;
    		i=j+1;
    	}
    	sort(pos0+1,pos0+ct0+1);
    	sort(pos1+1,pos1+ct1+1);
    	int s=ct0+ct1+1,t=s+1;
    	tot=1;
    	for(int i=1;i<=ct0;++i){
    		add_edge(s,i,1);
    		for(int j=1;j<=ct1;++j){
    			int d=abs(pos1[j]-pos0[i]);assert(d<=N);
    			if(!v[d])add_edge(i,ct0+j,1);
    		}
    	}
    	for(int i=1;i<=ct1;++i)add_edge(ct0+i,t,1);
    	int ans=maxflow(s,t);
    	ct0-=ans;ct1-=ans;
    	ans+=(ct0/2)*2+(ct1/2)*2;
    	if(ct0&1)ans+=3;
    	cout<<ans<<endl;
    	return 0;
    }
    

    day3-序列

    题目链接

    考虑要使原序列前(i)个位置的和是(m)的倍数。发现,不管前(i-1)个位置怎么填,第(i)个位置都有且仅有唯一一种填法,使得和是(m)的倍数。同理,也会有恰好(m-1)种填法,使得和不是(m)的倍数。

    于是问题就转化为,钦定(j) ((jgeq k))个位置前缀和是(m)的倍数,其他位置都不是。因此答案等于:

    [sum_{j=k}^{n}{nchoose j}(m-1)^{n-j} ]

    预处理阶乘和逆元,可以(O(1))求组合数。总时间复杂度(O(n))

    参考代码(片段):

    const int MAXN=1e5,MOD=998244353;
    inline int mod(int x){return x<MOD?x:x-MOD;}
    inline int pow_mod(int x,int i){
    	int y=1;
    	while(i){
    		if(i&1)y=(ll)y*x%MOD;
    		x=(ll)x*x%MOD;
    		i>>=1;
    	}
    	return y;
    }
    int n,m,K,fac[MAXN+5],invf[MAXN+5];
    inline int comb(int n,int k){
    	if(n<k)return 0;
    	return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
    }
    int main() {
    	fac[0]=1;for(int i=1;i<=MAXN;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    	invf[MAXN]=pow_mod(fac[MAXN],MOD-2);
    	for(int i=MAXN-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
    	int T;cin>>T;while(T--){
    		cin>>n>>m>>K;
    		if(K>n){puts("0");continue;}
    		int t=pow_mod(m-1,n-K),iv=pow_mod(m-1,MOD-2),ans=0;
    		for(int i=K;i<=n;++i){
    			ans=mod(ans+(ll)comb(n,i)*t%MOD);
    			t=(ll)t*iv%MOD;
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

    day3-图

    题目链接

    考虑给图上的点染色,使得在最终的答案路径中,(k)个点的颜色都各不相同。这样我们就可以状压DP解决。设(dp[s][u])表示已经经过了(s)里的这些颜色,走到了点(u),经过的最长路径长度。转移时枚举下一步走到哪里。时间复杂度(O(2^k(n+m)))

    现在问题是我们还不知道这个染色方案。考虑随机染色:每个点的颜色在(k)种颜色里随机。

    这样总共有(k^n)种染色方案。其中能使得答案路径上点颜色各不相同的染色方案有(k!cdot k^{n-k})。正确率是(frac{k!cdot k^{n-k}}{k^n}=frac{k!}{k^k})

    多次随机,可以通过本题。

    参考代码(片段):

    const int MAXN=3e4;
    struct EDGE{int nxt,to,w;}edge[200005];
    int n,m,K,head[MAXN+5],tot,ans,f[MAXN+5][1<<8],col[MAXN+5],M;
    inline void add_edge(int u,int v,int w){edge[++tot].nxt=head[u];edge[tot].to=v;edge[tot].w=w;head[u]=tot;}
    void solve(){
    	for(int i=1;i<=n;++i){
    		col[i]=rand()%K;
    		for(int j=0;j<M;++j)f[i][j]=-1;
    		f[i][1<<col[i]]=0;
    	}
    	for(int s=1;s<M-1;++s){
    		for(int u=1;u<=n;++u){
    			if(f[u][s]<0)continue;
    			for(int i=head[u];i;i=edge[i].nxt){
    				int v=edge[i].to;
    				if(!(s&(1<<col[v])) && f[v][s^(1<<col[v])]<f[u][s]+edge[i].w){
    					f[v][s^(1<<col[v])]=f[u][s]+edge[i].w;
    				}
    			}
    		}
    	}
    	for(int i=1;i<=n;++i)ans=max(ans,f[i][M-1]);
    }
    int main() {
    	srand((ull)"dysyn1314");
    	cin>>n>>m>>K;
    	for(int i=1,u,v,w;i<=m;++i){cin>>u>>v>>w;add_edge(u,v,w);add_edge(v,u,w);}
    	int beg=clock();M=1<<K;ans=-1;
    	while(1.0*(clock()-beg)/CLOCKS_PER_SEC<1.5)solve();
    	cout<<ans<<endl;
    	return 0;
    }
    

    day3-商店

    题目链接

    简化一下问题。有三个序列(a[1dots n],b[1dots n],c[1dots n])。满足 (forall i: a[i]geq b[i])。你要选出一个({1,dots,n})的子集(S)。满足:

    [left(sum_{iin S}a[i] ight)geq p\ left(sum_{iin S}b[i] ight)leq p ]

    并且最大化:

    [sum_{iin S} c[i] ]

    朴素的DP:设(dp[i][x][y])表示考虑了前(i)个物品,选出的物品(a)之和为(x)(b)之和为(y),能得到的最大的(c)之和。转移时考虑当前物品选或不选,是(O(1))的。总时间复杂度(O(np^2))

    发现这个DP没有用到一个重要的条件:(forall i:a[i]geq b[i])

    考虑这样一个状态:(dp[i][z]),表示考虑了前(i)个物品,选出的物品(a)之和(geq z)(b)之和(leq z)。发现每个(z-a[i]leq z'leq z-b[i]),都可以转移到(z)。也就是说,对每个(i),我们枚举(z)之后,能转移到每个(z)的是一段长度相同的滑动窗口。用单调队列维护这段滑动窗口里(dp[i-1][z'])的最大值即可。

    时间复杂度(O(np))。空间可以用滚动数组优化到(O(n+p))

    参考代码(片段):

    int n,p,a[1005],b[1005],c[1005];
    ll dp[2][100005];
    int main() {
    	cin>>n>>p;
    	for(int i=1;i<=n;++i)cin>>a[i];
    	for(int i=1;i<=n;++i)cin>>b[i];
    	for(int i=1;i<=n;++i)cin>>c[i];
    	memset(dp,0x3f,sizeof(dp));dp[0][0]=0;
    	for(int i=1,ti=1;i<=n;++i,ti^=1){
    		deque<int>dq;
    		for(int j=0;j<=p;++j){
    			while(!dq.empty()&&dq.front()<j-a[i])dq.pop_front();
    			if(j-b[i]>=0){
    				while(!dq.empty()&&dp[ti^1][dq.back()]>=dp[ti^1][j-b[i]])dq.pop_back();
    				dq.push_back(j-b[i]);
    			}
    			dp[ti][j]=dp[ti^1][j];
    			if(!dq.empty())dp[ti][j]=min(dp[ti][j],dp[ti^1][dq.front()]+c[i]);
    		}
    	}
    	cout<<dp[n&1][p]<<endl;
    	return 0;
    }
    

    day9-谦逊

    题目链接

    引理1:任何一个数经过若干次谦逊操作,一定能得到一个小于等于(9)的数。并且,假设原数是(x),则得到的这个“小于等于(9)的数”就是((x-1)mod 9+1)。也就是说,对于$x=1,2,dots $,最终得到的结果,每(9)个数为一周期,不断循环。

    引理2(forall k)(i imes kmod 9)的值,对所有(i=0,1,2,dots),每(9)个数为一周期,永远循环。

    引理3:任何一个小于(10^{16})的数,最多需要用(3)谦逊操作,就能变成小于等于(9)的数。

    以上三条引理都可以通过简单的找规律或逻辑分析得出。此处略去证明。


    根据引理1可知,任何数使用谦逊的结果,只和它模(9)的余数有关。又根据引理2,对于一个(k),想要得到所有可能的(9)余数,只需要使用不超过(8)力量祝福。综上,我们最多使用(8)力量祝福,一定能得到最优答案。

    容易想到的一种做法是,枚举使用力量祝福的次数(i) ((0leq ileq 8)),用(( ext{digitSum}(n+i imes k),i+ ext{calc}(n+i imes k)))更新答案。其中( ext{digitSum})表示数位和,( ext{calc})表示变成小于等于(9)的数,需要使用的谦逊操作数。

    但上述的做法是不对的。虽然能求出最优的结果,却不一定能最小化操作次数。也就是说,有可能在中间某一步先搞一次谦逊操作,会比使用完所有力量祝福后再一起用谦逊更优。

    那怎么办呢?如果你转而去想高妙的贪心,或挖掘更深的性质,就容易陷入自闭。此时需要用到引理3,也就是( ext{calc}(n+i imes k))的值不超过(3)。所以总操作次数最多为(11)。我们可以爆搜所有可能的操作方案!按最坏情况粗略估计,大约要搜(sum_{x=1}^{11}2^x< 2^{12})种操作方案,实际远远达不到。可以轻松AC本题。

    时间复杂度(O(?))

    参考代码(片段):

    ll sum_dig(ll x){
    	ll res=0;
    	while(x)res+=(x%10),x/=10;
    	return res;
    }
    ll N,K;
    pii ans;
    void dfs(int u,int v,ll n){
    	if(n<=9)
    		ckmin(ans, mk((int)n,u+v));
    	if(u+v==11)
    		return;
    	if(u<8){
    		dfs(u+1,v,n+K);
    	}
    	if(v<3){
    		dfs(u,v+1,sum_dig(n));
    	}
    }
    void solve_case(){
    	cin>>N>>K;
    	ans=mk(10,233);
    	dfs(0,0,N);
    	cout<<ans.fi<<" "<<ans.se<<endl;
    }
    int main() {
    	int T;cin>>T;while(T--){
    		solve_case();
    	}
    	return 0;
    }
    

    day9-排序

    题目链接

    官方题解给出的构造方案(原文):

    • 首先给每个格子钦点一个可接受的范围区间。
    • 然后依次一个一个处理左上角格子的数,把它移到应该去的格子里。 如上图,如果左上角最上层的盘子是(83),我们就把它移到((1,3))去, 然后处理下一个。
    • 然后按从大区间到小区间的次序把所有区间移到右下角,使它们最后是排好序的。如上图,先把([91−99])移到右下角,然后是([81−90]), 以此类推。

    也就是说,我们把左上角(6 imes6)的问题,转化成若干个小子问题。从大到小,依次递归解决每个子问题,最终就能把整个局面排好序了。

    那我们现在就要问:一个(n imes m)的矩阵,从左上角进、右下角出,最多能把多少个盘子排好序。我们可以严谨地说明一下,“进”就是指左上角的格子原本是空的,然后我们突然放一堆盘子进来,它们是乱序的;“出”就是指把这些盘子,按下面大、上面小的顺序排好后,堆叠到右下角的格子上方(右下角的格子里可能原本已有一些盘子,不过不影响。我们放在它们上面即可)。具体到本题中,其实所有的“右下角”,都是指整个(6 imes6)矩形的右下角,也就是坐标((6,6))的这个格子;而左上角,则不确定只哪个格子,取决于我们讨论的(n imes m)的大小。

    我们记,一个(n imes m)的矩阵,最多能把多少个盘子排好序,这个数量为(f(n,m))

    首先,(f(1,1)=1)

    然后根据前面所说,我们解决这个问题的方法其实就是分治(把要排序的数分发下去)。所以除了左上角外的每个格子,都被分发到一段数。因此他们能排序的量的总和,就是(n imes m)能排序的数量。即:(f(n,m)=sum_{i=1}^{n}sum_{j=1}^{m}[(i,j) eq (n,m)] imes f(i,j))

    按此式子计算,得出(f(6,6)=26928)。不足以通过本题。

    发现(1 imes 2)(2 imes 1)的格子,通过手动构造,能排好(2)个盘子而非(1)个。所以强制令(f(1,2)=f(2,1)=2)后,可以算出(f(6,6)=42960),可以通过本题。

    时间复杂度(O(n))

    具体的递归实现,可以见代码。希望本题可以帮助读者更好的理解“分治”和“排序”这两个基础概念。

    参考代码(片段):

    const int MAXN=4e4;
    int f[10][10];
    int n;
    struct Oper{
    	int r,c,num;
    	char d;
    	Oper(int _r,int _c,char _d,int _num){
    		r=_r; c=_c; d=_d; num=_num;
    	}
    	Oper(){}
    };
    vector<Oper>ans;
    void move(int frm_r,int frm_c,int to_r,int to_c){
    	int r=frm_r, c=frm_c;
    	while(r<to_r){
    		ans.pb(Oper(r,c,'D',1));
    		++r;
    	}
    	while(c<to_c){
    		ans.pb(Oper(r,c,'R',1));
    		++c;
    	}
    }
    void solve(int pos_r,int pos_c,int val_l,int val_r,vector<int> a){
    	if(!SZ(a)) return;
    	if(pos_r==6 && pos_c==6) return;
    	if(pos_r==5 && pos_c==6){
    		assert(SZ(a)<=2);
    		if(SZ(a)==1) move(5,6,6,6);
    		else{
    			if(a[0]>a[1]) ans.pb(Oper(5,6,'D',2));
    			else{ move(5,6,6,6); move(5,6,6,6); }
    		}
    		return;
    	}
    	if(pos_r==6 && pos_c==5){
    		assert(SZ(a)<=2);
    		if(SZ(a)==1) move(6,5,6,6);
    		else{
    			if(a[0]>a[1]) ans.pb(Oper(6,5,'R',2));
    			else{ move(6,5,6,6); move(6,5,6,6); }
    		}
    		return;
    	}
    	vector<vector<pii> > range(7,vector<pii>(7));
    	vector<vector<vector<int> > > vec(7,vector<vector<int> >(7));
    	int cur=val_l;
    	for(int i=pos_r;i<=6;++i){
    		for(int j=pos_c+(i==pos_r);j<=6;++j){
    			int size=f[6-i+1][6-j+1];
    			range[i][j]=mk(cur,cur+size-1);
    			cur+=size;
    		}
    	}
    	assert(cur-1>=val_r);
    	for(int k=SZ(a)-1;k>=0;--k){
    		int x=a[k];
    		assert(val_l<=x && val_r>=x);
    		bool flag=0;
    		for(int i=pos_r;i<=6;++i){
    			for(int j=pos_c+(i==pos_r);j<=6;++j){
    				if(range[i][j].fi<=x && range[i][j].se>=x){
    					vec[i][j].pb(x);
    					move(pos_r,pos_c,i,j);
    					flag=1;break;
    				}
    			}
    			if(flag)break;
    		}
    		assert(flag);
    	}
    	for(int i=6;i>=pos_r;--i){
    		for(int j=6;j>=pos_c+(i==pos_r);--j){
    			solve(i,j,range[i][j].fi,range[i][j].se,vec[i][j]);
    		}
    	}
    }
    int main() {
    	for(int i=1;i<=6;++i){
    		for(int j=1;j<=6;++j){
    			if(i==1 && j==1){
    				f[i][j]=1;
    				continue;
    			}
    			if((i==1 && j==2) || (i==2 && j==1)){
    				f[i][j]=2;
    				continue;
    			}
    			f[i][j]=0;
    			int tmp=0;
    			for(int x=1;x<=i;++x)for(int y=1;y<=j;++y){
    				tmp+=f[x][y];
    			}
    			f[i][j]=tmp;
    		}
    	}
    	cerr<<f[6][6]<<endl;
    	
    	cin>>n;
    	vector<int>a;
    	a.resize(n);
    	for(int i=0;i<n;++i){
    		cin>>a[i];
    	}
    	solve(1,1,1,n,a);
    	cout<<SZ(ans)<<endl;
    	for(int i=0;i<SZ(ans);++i){
    		cout<<ans[i].r<<" "<<ans[i].c<<" "<<ans[i].d<<" "<<ans[i].num<<endl;
    	}
    	return 0;
    }
    

    day9-猜拳

    再解决最后一点细节就来更新。

    day10-数学题

    题目链接

    假设已经知道了(p,q,k)。我们可以用一个二元组,来描述(n=p^kq^k)的每个约数:二元组((a,b))表示(p^aq^b) ((0leq a,bleq k))。

    考虑将所有这样的二元组,按(p^aq^b)排序。然后做一个DP。设(dp[i][x][y])表示考虑了前(i)个二元组,当前选出的二元组中,(p)的指数和为(x)(q)的指数和为(y),的方案数。由于同一个二元组可以选多次,我们可以按照完全背包的方法,把第一维去掉,然后每次从小到大枚举(x,y)

    答案就是(dp[k][k])

    我们发现,这个DP的结果,与(p,q)具体是多少无关。因为我们做的DP是一个背包,与物品的顺序没有关系。只要选出一个物品的集合,那一定能有唯一的一种方法将其排好序变成一个序列。

    既然与(p,q)无关,那么可以对每个(k),用上述的DP预处理它的答案。物品有(k^2)个,所以DP的时间复杂度是(O(k^4))的。因为(2^{24} imes3^{24}>10^{18}),所以(k)最大不超过(23)。枚举每个(k)做一遍这样的DP,复杂度是(23^5)

    然后处理每个询问,最关键的问题是要求出(k)。可以从(23)(1)枚举所有可能的(k),利用( exttt{C++})自带的( exttt{pow})函数对(n)(k)次根(也就是( exttt{pow((long double)n,1.0/k)})),然后再检验这个开根结果(取整后)的(k)次方是否等于(n)。由于精度问题,取整最好把一定范围内上下几个数都试一遍。

    ( exttt{pow})函数的时间复杂度是(P(n)),则总时间复杂度(O(23^5 + T imes 23 imes (P(n)+23)))。其中最后(P(n)+23)也可以变成(P(n)+log 23),取决于检验开根结果时,是否使用快速幂。

    参考代码(片段。需添加读入优化):

    // 请添加读入优化!!!
    
    ll n;
    ll ans[25],dp[25][25];
    void init(){
    	for(int K=1;K<=23;++K){
    		memset(dp,0,sizeof(dp));
    		dp[0][0]=1;
    		for(int i=0;i<=K;++i){
    			for(int j=(i==0);j<=K-(i==K);++j){
    				for(int x=0;x<=K-i;++x){
    					for(int y=0;y<=K-j;++y){
    						dp[x+i][y+j]+=dp[x][y];
    					}
    				}
    			}
    		}
    		ans[K]=dp[K][K];
    		cerr<<ans[K]<<endl;
    	}
    }
    inline ll qpow(ll x,int i){
    	ll y=1;
    	while(i){
    		if(i&1){
    			y*=x;
    		}
    		x*=x;
    		i>>=1;
    	}
    	return y;
    }
    void solve_case(){
    	cin>>n;
    	for(int i=23;i>=1;--i){
    		ll pq = pow((long double)n,1.0/i);
    		for(ll x=max(pq-1,1LL);x<=pq+1;++x){
    			ll pw=qpow(x,i);
    //			for(int j=1;j<=i;++j){
    //				pw*=x;
    //			}
    			if(pw==n){
    				cout<<ans[i]<<endl;
    				return;
    			}
    		}
    	}
    	cerr<<n<<endl;
    	assert(0);
    }
    int main() {
    	init();
    	int T;cin>>T;while(T--){
    		solve_case();
    	}
    	return 0;
    }
    

    day10-城市

    对于两个城市 (i,j),如果 (a_ioperatorname{and}a_j eq 0),题目说它们之间会有一条边权为 ( ext{lowbit}(a_i operatorname{and} a_j)) 的边。但其实,我们可以对所有 (a_ioperatorname{and}a_j) 里是 (1) 的二进制位,都连一条对应位权的边。显然这样不会改变答案。

    于是问题转化为,对于一个点 (i),检查它的每个为 (1) 的二进制位 (k) ((0leq k<32))。然后从 (i) 向所有二进制下第 (k) 位为 (1) 的点连一条边权为 (2^k) 的边。

    既然是向某一类点集体连边,那么可以建虚点。也就是建 (32) 个点分别表示 (k=0dots 31),然后从每个 (i) 向这些虚点连边,再从虚点向对应的 (j) 连边。这样总边数是 (O(nlog a)) 的。跑一个最短路,时间复杂度 (O(nlog alog n))

    代码略。

    day10-好 ♂ 朋 ♂ 友

    注意到,一个序列的前缀 (operatorname{or}) 和只会变化 (O(log a)) 次。因为每次的变化,相当于把一些位变成 (1),而某一位一旦从 (0) 变成 (1),就不会再改变了。

    把询问离线。从大到小枚举询问的左端点。每次添加一个可能的,子段的左端点 (i)。考虑从 (i)(n) 的这段序列,它的前缀 (operatorname{or}) 和总共会变化 (O(log a)) 次。这些变化的位置可以 (O(nlog a)) 预处理出来,做法是从后往前,递推求 ( ext{nxt}(i,j)) 表示位置 (i) 后面第一个第 (j) 位为 (1) 的数在哪。

    枚举这 (O(log a)) 段东西,若数值合法,则考虑这一段对询问的贡献。用一个数组维护,以每个 (r) 为右端点的合法子段数量。则每次的贡献,相当于对这个数组做区间加。回答询问则相当于是区间求和。可以用线段树维护。

    时间复杂度(O(m log n + nlog alog n))

    参考代码:

    // problem: nflsoj475
    #include <bits/stdc++.h>
    using namespace std;
    
    #define mk make_pair
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    /* --------------- fast io --------------- */ // begin
    namespace Fread {
    const int SIZE = 1 << 21;
    char buf[SIZE], *S, *T;
    inline char getchar() {
    	if (S == T) {
    		T = (S = buf) + fread(buf, 1, SIZE, stdin);
    		if (S == T) return '
    ';
    	}
    	return *S++;
    }
    } // namespace Fread
    namespace Fwrite {
    const int SIZE = 1 << 21;
    char buf[SIZE], *S = buf, *T = buf + SIZE;
    inline void flush() {
    	fwrite(buf, 1, S - buf, stdout);
    	S = buf;
    }
    inline void putchar(char c) {
    	*S++ = c;
    	if (S == T) flush();
    }
    struct NTR {
    	~ NTR() { flush(); }
    } ztr;
    } // namespace Fwrite
    #ifdef ONLINE_JUDGE
    	#define getchar Fread :: getchar
    	#define putchar Fwrite :: putchar
    #endif
    namespace Fastio {
    struct Reader {
    	template<typename T>
    	Reader& operator >> (T& x) {
    		char c = getchar();
    		T f = 1;
    		while (c < '0' || c > '9') {
    			if (c == '-') f = -1;
    			c = getchar();
    		}
    		x = 0;
    		while (c >= '0' && c <= '9') {
    			x = x * 10 + (c - '0');
    			c = getchar();
    		}
    		x *= f;
    		return *this;
    	}
    	Reader& operator >> (char& c) {
    		c = getchar();
    		while (c == '
    ' || c == ' ') c = getchar();
    		return *this;
    	}
    	Reader& operator >> (char* str) {
    		int len = 0;
    		char c = getchar();
    		while (c == '
    ' || c == ' ') c = getchar();
    		while (c != '
    ' && c != ' ') {
    			str[len++] = c;
    			c = getchar();
    		}
    		str[len] = '';
    		return *this;
    	}
    	Reader(){}
    } cin;
    const char endl = '
    ';
    struct Writer {
    	template<typename T>
    	Writer& operator << (T x) {
    		if (x == 0) { putchar('0'); return *this; }
    		if (x < 0) { putchar('-'); x = -x; }
    		static int sta[45];
    		int top = 0;
    		while (x) { sta[++top] = x % 10; x /= 10; }
    		while (top) { putchar(sta[top] + '0'); --top; }
    		return *this;
    	}
    	Writer& operator << (char c) {
    		putchar(c);
    		return *this;
    	}
    	Writer& operator << (char* str) {
    		int cur = 0;
    		while (str[cur]) putchar(str[cur++]);
    		return *this;
    	}
    	Writer& operator << (const char* str) {
    		int cur = 0;
    		while (str[cur]) putchar(str[cur++]);
    		return *this;
    	}
    	Writer(){}
    } cout;
    } // namespace Fastio
    #define cin Fastio :: cin
    #define cout Fastio :: cout
    #define endl Fastio :: endl
    /* --------------- fast io --------------- */ // end
    
    const int MAXN = 1e5;
    const int MAXM = 1e6;
    const int LOGA = 29;
    
    int n, m, K;
    bool good[10];
    int a[MAXN + 5], nxt[LOGA + 1];
    
    vector<pii> qs[MAXN + 5];
    ll ans[MAXM + 5];
    
    class SegmentTree {
    private:
    	ll sum[MAXN * 4 + 5], tag[MAXN * 4 + 5];
    	void push_down(int p, int l, int mid, int r) {
    		if (tag[p]) {
    			sum[p << 1] += tag[p] * (mid - l + 1);
    			tag[p << 1] += tag[p];
    			sum[p << 1 | 1] += tag[p] * (r - mid);
    			tag[p << 1 | 1] += tag[p];
    			
    			tag[p] = 0;
    		}
    	}
    	void push_up(int p) {
    		sum[p] = sum[p << 1] + sum[p << 1 | 1];
    	}
    	void range_add(int p, int l, int r, int ql, int qr, ll v) {
    		if (ql <= l && qr >= r) {
    			sum[p] += v * (r - l + 1);
    			tag[p] += v;
    			return;
    		}
    		
    		int mid = (l + r) >> 1;
    		push_down(p, l, mid, r);
    		
    		if (ql <= mid) {
    			range_add(p << 1, l, mid, ql, qr, v);
    		}
    		if (qr > mid) {
    			range_add(p << 1 | 1, mid + 1, r, ql, qr, v);
    		}
    		
    		push_up(p);
    	}
    	ll range_query(int p, int l, int r, int ql, int qr) {
    		if (ql <= l && qr >= r) {
    			return sum[p];
    		}
    		
    		int mid = (l + r) >> 1;
    		push_down(p, l, mid, r);
    		
    		ll res = 0;
    		if (ql <= mid) {
    			res = range_query(p << 1, l, mid, ql, qr);
    		}
    		if (qr > mid) {
    			res += range_query(p << 1 | 1, mid + 1, r, ql, qr);
    		}
    		
    		return res;
    	}
    public:
    	void range_add(int l, int r, ll v = 1) {
    		range_add(1, 1, n, l, r, v);
    	}
    	ll range_query(int l, int r) {
    		return range_query(1, 1, n, l, r);
    	}
    	SegmentTree() {}
    } T;
    
    int main() {
    	cin >> n >> m >> K;
    	
    	for (int i = 1; i <= K; ++i) {
    		int x;
    		cin >> x;
    		good[x] = 1;
    	}
    	
    	for (int i = 1; i <= n; ++i) {
    		cin >> a[i];
    	}
    	
    	for (int i = 1; i <= m; ++i) {
    		int l, r;
    		cin >> l >> r;
    		qs[l].push_back(mk(r, i));
    	}
    	
    	for (int i = n; i >= 1; --i) {
    		vector<pii> events;
    		for (int j = 0; j <= LOGA; ++j) {
    			if ((a[i] >> j) & 1)
    				nxt[j] = i; // [i, n] 里最小的, 第 j 位为 1 的位置
    			if (nxt[j])
    				events.push_back(mk(nxt[j], 1 << j));
    		}
    		events.push_back(mk(i, 0));
    		sort(events.begin(), events.end());
    		int cur_num = 0;
    		for (int j = 0; j < SZ(events); ++j) {
    			int jj = j;
    			while (jj + 1 < SZ(events) && events[jj + 1].fi == events[j].fi)
    				++jj;
    			for (int t = j; t <= jj; ++t) {
    				cur_num |= events[t].se;
    			}
    			
    			if (good[cur_num % 10]) {
    				int nxt_pos = ((jj == SZ(events) - 1) ? n + 1 : events[jj + 1].fi);
    				T.range_add(events[j].fi, nxt_pos - 1);
    			}
    			
    			j = jj;
    		}
    		for (int j = 0; j < SZ(qs[i]); ++j) {
    			ans[qs[i][j].se] = T.range_query(i, qs[i][j].fi);
    		}
    	}
    	for (int i = 1; i <= m; ++i) {
    		cout << ans[i] << endl;
    	}
    	return 0;
    }
    

    day11-全国高中 IO 联赛一试

    算期望比较麻烦。可以先求和,再除以总方案数 (frac{n!}{prod_{j=1}^{k}c_j!})

    对于每种选项 (i),考虑以它为答案的每道题,对总和的贡献。钦定这道题做对了,其他题随便填的方案数是 (frac{(n-1)!}{prod_{j=1}^{k}(c_j-[j=i])!})。这样的题有 (a_i) 道,所以答案就是:

    [frac{sum_{i=1}^{k}a_ifrac{(n-1)!}{prod_{j=1}^{k}(c_j-[j=i])!}}{frac{n!}{prod_{j=1}^{k}c_j!}} ]

    因为是小数运算,我们不可能直接算这么大的阶乘。所以需要化简式子,得到:

    [sum_{i=1}^{k}frac{a_ic_i}{n} ]

    直接算这个即可。时间复杂度 (O(k))

    代码略。

    day11-纸条

    由于官方题解写的非常好,所以我直接丢官方题解了。

    download

    day11-全国高中 IO 联赛二试

    考虑已知点集,求最小生成树的方法。

    任取一个点为根(不一定是点集里的点)。设点 (i) 到根的距离为 (d_i)。把点集里所有点,按 (d_i) 从小到大排序。令第一个点((d_i) 最小的点)作为生成树的根。从第二个点开始,每次选当前点前面的、与当前点距离最小的点,作为当前点(在生成树上)的父亲,并把当前点加入生成树。

    这种做法的正确性证明(简要思路):

    设按 (d_i) 从小到大排好序后,每个点的位置为 (p_i)

    只需要证明,存在一个生成树,满足以 (p_{ ext{root}}=1) 的点 ( ext{root}) 为根时,其他点都满足 (p_{fa(i)}<p_i)。其中 (fa(i)) 是点 (i) 在生成树上的父亲。

    可以先任取一个最小生成树。然后取最大的、满足 (p_{fa(u)}>p_{u}) 的点 (u)。令 (fa(u):=fa(fa(u)))。可以证明,这样边权和一定不会变大。

    并且由于 (u)最大的满足 (p_{fa(u)}>p_u) 的点,所以 (p_{fa(fa(u))}) 一定 (<p_{fa(u)}),因此有限次操作后,一定能消除所有 (p_{fa(u)}>p_u) 的点。

    详细证明

    根据这种方法,我们求出本题答案就比较简单了。

    利用期望的线性性,考虑个点到它(生成树上)的父亲的距离,对答案的贡献。枚举这个点 (u),把所有(按 (d_i) 排序后)排在 (u) 前面的点,按与 (u) 的距离排序。在这些点里,枚举 (u)(生成树上)的父亲 (v),则所有排在 (v) 前面的点,都一定不能在点集里。算出这样的概率,乘以 (u,v) 之间的距离,加入答案。

    时间复杂度 (O(n^2))

    day12-T2-枣子

    题目链接

    考虑某个正整数 (t),它可以表示为 (x^y) ((y>1)) 的形式,当且仅当 (y) 是【 (t) 的所有质因子的次数】的约数。具体地,设分解质因数后 (t=p_1^{e_1}p_2^{e_2}cdots p_k^{e_k}),则 (y|gcd(e_1,e_2,dots,e_k))。设最大的、合法的 (y)(g(t)),则 (g(t)=gcd(e_1,e_2,dots,e_k)),且所有其他的、合法的 (y) 都是 (g(t)) 的约数。

    (y = g(a))(a=x^y)。则原数 (a^{(b^c)}) 可以写成 (x^{(yb^c)}),且根据 (g) 函数的定义,(x) 不能再被表示为任何数的 (>1) 次幂。

    我们先预处理一个 (dp(i)),表示一个形如 (u^i) 的数,有多少种表示方法。其中 (u) 将作为一个整体不再被拆解(不会被拆成任何数的 (>1) 次幂,你可以把 (u) 想象为上一段中的 (x) )。DP 的转移是考虑最底下的数,是 (u) 的几次方。例如,最底下的数是 (u^j)(j)(i) 的约数),那么它的指数就应该是 (frac{i}{j}),并且这个指数还能继续被拆分,拆分它的方案数就是 (dp(g(frac{i}{j}))),所以 (dp(i) = sum_{j|i}dp(g(frac{i}{j})))。通过巧妙地枚举 (frac{i}{j})(例如将 (i) 分解质因数后 dfs),我们可以顺便知道(g(frac{i}{j}))。设 DP 数组长度为 (L),这个 DP 复杂度是 (O(Lsqrt{L})) 的。

    理论上,只要这个 DP 数组的大小高达 (yb^c) 这么大,我们就能直接报出答案。(不过答案并不是 (dp(yb^c)),因为题目要求枣子塔高度 (geq 3),而我们 DP 时并没有考虑这个要求。不过这并不本质,只要稍微修改一下 DP 的定义就能解决这一问题)。

    但显然,DP 数组不可能有 (yb^c) 这么大,所以这个算法还需改进。

    考虑最终的结果(也就是这个等于 (a^{(b^c)}) 的枣子塔)里,最底下的数是什么。发现它一定可以被表示为 (x^k),其中 (k)(yb^c) 的一个约数。如果枚举了 (k),此时的方案数就是:堆出值等于 (frac{yb^c}{k}) 的、高度 (geq 2) 的枣子塔的方案数,也就是 (dp(g(frac{yb^c}{k})) - 1)。其中 (-1) 是去掉唯一的那个高度为 (1) 的枣子塔。

    于是答案就等于:(sum_{k|yb^c}left(dp(g(frac{yb^c}{k})) - 1 ight)),令 (i = frac{yb^c}{k}),则也可以更简洁地写成:

    [ ext{ans}=sum_{i|yb^c}left(dp(g(i)) - 1 ight) ]

    虽然 (yb^c) 极大,它的约数也极多,但将它分解质因数却并不困难,我们只要把 (y)(b) 分别分解质因数,再合并起来即可。

    知道了它的质因数构成后,我们枚举 (g(i)) 的值,设为 (j)(显然 (j) 小于等于 (yb^c) 质因子的最大次数,也就是 (O(log y+clog b)) ),此时 (i) 里所有质因子次数,都必须是 (j) 的倍数,那我们枚举每个质因子 (p_t),设它的出现次数为 (e_t),则方案数是 (prod_{t}(lfloorfrac{e_t}{j} floor +1 ))。但这还包含了 (g(i))(j) 的倍数的情况,将它们减掉即可:暴力枚举倍数,总复杂度是调和级数,这是个经典套路了。

    因为 (g(i)) 的值不超过 (O(log y + clog b)=O(loglog a + c log b)),所以 DP 数组也只需要预处理这么长。设 (L = clog b),则该算法的总时间复杂度是 (O(Lsqrt{L} +TLlog L))。可以通过本题。

    参考代码:

    //problem:nflsoj480
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    const int MAXN = 3e4, LOG = 20;
    const int MAXG = (MAXN + 1) * LOG;
    const int MOD = 998244353;
    inline int mod1(int x) { return x < MOD ? x : x - MOD; }
    inline int mod2(int x) { return x < 0 ? x + MOD : x; }
    inline void add(int &x, int y) { x = mod1(x + y); }
    inline void sub(int &x, int y) { x = mod2(x - y); }
    
    int gcd(int x, int y) { return (!y) ? x : gcd(y, x % y); }
    
    int a, b, c;
    vector<pii> prm_a, prm_b;
    vector<pii> decompose(int x) {
    	vector<pii> p;
    	for(int i = 2; i * i <= x; ++i) {
    		if(x % i == 0) {
    			p.pb(mk(i, 0));
    			while(x % i == 0) {
    				x /= i;
    				p.back().se++;
    			}
    		}
    	}
    	if(x != 1) {
    		p.pb(mk(x, 1));
    	}
    	return p;
    }
    
    int dp[MAXG + 5];
    void dfs(int idx, int g, int v, int id, const vector<pii>& p) {
    	if(idx == SZ(p)) {
    		if(v > 1) {
    			add(dp[id], dp[g]);
    		}
    		return;
    	}
    	int vv = v;
    	for(int i = 0; i <= p[idx].se; ++i) {
    		dfs(idx + 1, gcd(g, i), vv, id, p);
    		vv *= p[idx].fi;
    	}
    }
    void init() {
    	dp[1] = 1;
    	for(int i = 2; i <= MAXG; ++i) {
    		dp[i] = 1;
    		vector<pii> p = decompose(i);
    		dfs(0, 0, 1, i, p);
    	}
    	// cerr << "yes" << endl;
    	// for(int i = 1; i <= 10; ++i) cerr << dp[i] << " "; cerr << endl;
    }
    int w[MAXG + 5];
    void solve_case() {
    	cin >> a >> b >> c;
    	prm_a = decompose(a);
    	prm_b = decompose(b);
    	int g = prm_a[0].se;
    	for(int i = 1; i < SZ(prm_a); ++i) g = gcd(g, prm_a[i].se);
    	
    	vector<pii> tmp = decompose(g);
    	vector<pii> p;
    	int i = 0, j = 0;
    	while(i < SZ(tmp) && j < SZ(prm_b)) {
    		if(tmp[i].fi == prm_b[j].fi) {
    			p.pb(mk(tmp[i].fi, tmp[i].se + prm_b[j].se * c));
    			++i; ++j;
    		} else if(tmp[i].fi < prm_b[j].fi) {
    			p.pb(mk(tmp[i].fi, tmp[i].se));
    			++i;
    		} else {
    			p.pb(mk(prm_b[j].fi, prm_b[j].se * c));
    			++j;
    		}
    	}
    	while(i < SZ(tmp)) {
    		p.pb(mk(tmp[i].fi, tmp[i].se));
    		++i;
    	}
    	while(j < SZ(prm_b)) {
    		p.pb(mk(prm_b[j].fi, prm_b[j].se * c));
    		++j;
    	}
    	int lim = 0;
    	for(int j = 0; j < SZ(p); ++j) {
    		assert(p[j].se <= MAXG);
    		ckmax(lim, p[j].se);
    	}
    	
    	for(int i = 1; i <= lim; ++i) {
    		w[i] = 1;
    		for(int j = 0; j < SZ(p); ++j) {
    			w[i] = (ll)w[i] * (p[j].se / i + 1) % MOD;
    		}
    		sub(w[i], 1);
    	}
    	int ans = 0;
    	for(int i = lim; i >= 2; --i) {
    		for(int j = i + i; j <= lim; j += i) {
    			sub(w[i], w[j]);
    		}
    		// if(w[i]) cerr << i << " " << w[i] << endl;
    		ans = ((ll)ans + (ll)w[i] * mod2(dp[i] - 1)) % MOD;
    	}
    	cout << ans << endl;
    }
    int main() {
    //	freopen("jujube.in", "r", stdin);
    //	freopen("jujube.out", "w", stdout);
    	init();
    	int T; cin >> T; while(T--) {
    		solve_case();
    	}
    	return 0;
    }
    

    day12-T3-开关

    题目要求的是集合数量,也就是忽略顺序的。不过由于题目强制了 (m) 次操作互不相同,我们可以先求出带顺序的答案,再除以 (m!) 。具体来说,记原答案是 (f_m(v)),我们令 (g_m(v) = m!f_m(v)),现在考虑 (g_m(v)) 怎么求。

    考虑枚举前 (m-1) 个是啥,那么最后一个也就确定了。这样方案数是 ({2^nchoose m-1}(m-1)!)

    但这样可能会计算到一些不合法的情况,也就是我们的最后一次操作,和前面某个操作相等。那么由于这两个操作相等,它们异或和为 (0),所以其他的 (m-2) 个操作的异或和就是我们想要的值(前 (v) 位为 (1) 的这个二进制数),并且这 (m-2) 个操作一定互不相等(因为是通过组合数 ({2^nchoose m-1}) 选出来的)。也就是说,这 (m-2) 个操作,形成了一个子问题,它们的方案数就是:(g_{m-2}(v))

    如果枚举,最后一次操作和前面哪次操作相等,那么不合法的总方案数就是:((m-1)(2^n-(m-2))g_{m-2}(v))。其中 (m-1) 是枚举哪次操作相等,((2^n-(m-2))) 是这两个操作可以选择的值(不能和其他 (m-2) 个操作相同),最后其他操作的方案数就是 (g_{m-2}(v))

    预处理出 ({2^nchoose 0},{2^nchoose 1},{2^nchoose 2},dots ,{2^nchoose m}) 的值,然后就能 (O(m)) 递推了。

    总时间复杂度 (O(Tm))

    参考代码:

    //problem:nflsoj481
    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    
    const int MAXM = 1000;
    const int MOD = 19260817;
    inline int mod1(int x) { return x < MOD ? x : x - MOD; }
    inline int mod2(int x) { return x < 0 ? x + MOD : x; }
    inline void add(int &x, int y) { x = mod1(x + y); }
    inline void sub(int &x, int y) { x = mod2(x - y); }
    inline int pow_mod(int x, int i) {
    	int y = 1;
    	while(i) {
    		if(i & 1) y = (ll)y * x % MOD;
    		x = (ll)x * x % MOD;
    		i >>= 1;
    	}
    	return y;
    }
    int fac[MAXM + 5], ifac[MAXM + 5], inv[MAXM + 5];
    void facinit() {
    	fac[0] = 1;
    	for(int i = 1; i <= MAXM; ++i) fac[i] = (ll)fac[i - 1] * i % MOD;
    	ifac[MAXM] = pow_mod(fac[MAXM], MOD - 2);
    	for(int i = MAXM - 1; i >= 0; --i) ifac[i] = (ll)ifac[i + 1] * (i + 1) % MOD;
    	for(int i = 1; i <= MAXM; ++i) inv[i] = (ll)ifac[i] * fac[i - 1] % MOD;
    }
    
    
    int n, m, v;
    int _2n, comb_2n[MAXM + 5]; // C(2^n, 1...m)
    int ans[MAXM + 5];
    void case_init() {
    	// 已知 m,n 
    	comb_2n[0] = 1;
    	_2n = pow_mod(2, n);
    	for(int i = 1; i <= m; ++i) {
    		comb_2n[i] = (ll)comb_2n[i - 1] * mod2(_2n - i + 1) % MOD * inv[i] % MOD;
    	}
    }
    void solve_case() {
    	cin >> n >> m >> v;
    	case_init();
    	ans[0] = (!v);
    	ans[1] = 1;
    	for(int i = 2; i <= m; ++i) {
    		ans[i] = mod2((ll)comb_2n[i - 1] * fac[i - 1] % MOD - (ll)ans[i - 2] * mod2(_2n - (i - 2)) % MOD * (i - 1) % MOD);
    	}
    	int Ans = (ll)ans[m] * ifac[m] % MOD;
    	cout << Ans << endl;
    }
    int main() {
    	facinit();
    	int T; cin >> T; while(T--) {
    		solve_case();
    	}
    	return 0;
    }
    

    day12-A

    题目链接

    考虑 (S)(T) 从前往后第一个不同的位置 (x),以及从后往前第一个不同的位置 (y)。 一个核心的观察是,如果存在合法的翻转方案,最短的翻转串一定就是 ([x,y])。并且其他翻转串一定都是 ([x-i, y+i]) 的形式,且 (i)(0,1,dots) 连续的一段。如果知道了 (S,T) 的哈希情况,这个最大的 (i) 我们可以二分出来。

    可以用线段树维护哈希值,在线段树上二分。

    注意特判 (S,T) 相等的情况,此时所有合法的翻转串,就是 (T) 里的回文串。由于 (T) 不会变,我们可以二分 + 哈希预处理出来。

    时间复杂度 (O((|S| + m)log |S|))

    day15-T1-小 D 与原题

    题目链接

    相当于要把 ((1,2),(1,3),dots,(1,n),(2,3),(2,4),dots,(2,n),dotsdots,(n-1,n))(frac{n(n-1)}{2}) 对元素,分成 (n-1) 组,使得每组不出现重复的数字。

    首先,((1,2),(1,3),dots,(1,n)),这 (n-1) 对元素中,任意两对都不可能出现在同一组(因为它们都有 (1)),所以不妨先将它们分别放在每一组的第一个。

    现在,每一组还剩 (n-2) 个数字(即 (frac{n-2}{2}) 对元素)要放。对于第 (i) 组(也就是第一对元素为 ((1,i)) 的组),我们维护两个指针 (x,y),初始时都等于 (i)。每次构造下一对数字前,令 (x) 往前移动一步(y) 往后移动一步。具体来说:

    [x' = egin{cases} x-1 && ext{if }x>2\ n && ext{if }x=2 end{cases} ]

    [y' = egin{cases} y + 1 && ext{if }y < n\ 2 && ext{if }y =n end{cases} ]

    这样它们最终会各自走 (frac{n-2}{2}) 步,恰好经过所有 (n-2) 个数字。相当于两个人,从同一起点出发,分别顺时针、逆时针走一个半圆,最后拼成一个整圆。所以肯定同一组里,一定不会经过重复的数字

    另外,可以发现,同一组里的每一对 ((x,y)),在半圆上的对称点相等(就是出发时的 (i)),不同组则不等。也可以用公式表示为,每一组具有一个唯一的 (((x-2)+(y-2))mod(n-1)) 的值。所以不同组里,一定不会出现同一对 ((x,y))

    综合上述两点,我们的构造方案是正确的。

    参考代码(片段):

    int main() {
    	int n; cin >> n;
    	int len = n / 2 - 1;
    	for(int i = 2; i <= n; ++i) {
    		int x = i, y = i;
    		cout << 1 << " " << x << " ";
    		for(int j = 1; j <= len; ++j) {
    			x = (x == 2 ? n : x - 1);
    			y = (y == n ? 2 : y + 1);
    			cout << x << " " << y << " ";
    		}
    		cout << endl;
    	}
    	return 0;
    }
    

    day15-T2-小 D 与随机

    go

    day15-T3-小 D 与游戏

    特判 (nleq 3) 的情况。以下只讨论 (ngeq4)

    考虑把原串里 (a,b,c) 分别换成数字 (0,1,2)。发现每次操作字符串所有位上数字的和在 (mod 3) 意义下不变。那是不是所有数字和 (mod3) 相等的串,都能通过若干次操作相互达到呢?带着这个疑问,我们爆搜出 (n=4) 的情况。发现可以分成三类:

    1. 如果整个串所有字符都相等(即 (S_1=S_2=S_3=S_4)),那么它不能做任何变化,所以答案是 (1)
    2. 除第 1 类外,如果存在一对相邻的字符相等,则答案是 (19)
    3. 如果不存在一对相邻的字符相等,则答案是 (20)

    这比较好解释,因为如果不存在相邻、相等的字符,那这个串一定不是通过操作得到的(因为只要经过了操作,就一定存在相邻、相等的字符),所以这样的串答案比其他串多了一个它自己的初始状态。

    又注意到在 (n=4) 时,对于 (mod3) 的每个余数,串的总数只有 (1+18+8=27) 种(分别是上述三类串的数量)。所以对于 (n=4),我们得到的结论是:

    对于第 2 类的串,它能到达任意一个和它同余的,第 1 或第 2 类的串。

    对于第 3 类的串,它能到达任意一个和它同余的,第 1 或第 2 类的串;不过由于还要算上它自己的初始状态,所以它的答案比第 2 类的串多 (1)

    (n>4) 时,可以通过打表验证或归纳证明,这个结论是正确的。

    于是问题转化为求第 2 类串的数量,也就是:字符之和 (mod3) 与原串同余的、存在一对相邻字符相等的,串的数量。可以做一个简单 DP。设 (dp[i][xin{0,1,2}][yin{0,1,2}][zin{0,1}]),表示考虑了前 (i) 位,最后一位是 (x),字符之和 (mod3)(y),是否已经存在一对相邻、相等的字符,这样的串的数量。

    时间复杂度 (O(n))

    参考代码(片段):

    const int MAXN = 2e5;
    const int MOD = 998244353;
    inline int mod1(int x) { return x < MOD ? x : x - MOD; }
    inline int mod2(int x) { return x < 0 ? x + MOD : x; }
    inline void add(int &x, int y) { x = mod1(x + y); }
    inline void sub(int &x, int y) { x = mod2(x - y); }
    
    int n, dp[MAXN + 5][3][3][2];
    char s[MAXN + 5];
    
    map<vector<int>, bool> mp;
    void dfs(const vector<int>& v) {
    	if(mp.count(v)) return;
    	mp[v] = 1;
    	for(int i = 0; i <= SZ(v) - 2; ++i) if(v[i] != v[i + 1]) {
    		int x = 0;
    		for(; ; ++x) {
    			if(x != v[i] && x != v[i + 1]) break;
    		}
    		vector<int> vv = v;
    		vv[i] = vv[i + 1] = x;
    		dfs(vv);
    	}
    }
    int main() {
    	cin >> (s + 1);
    	n = strlen(s + 1);
    	if(n <= 3) {
    		vector<int> st;
    		for(int i = 1; i <= n; ++i) {
    			st.pb(s[i] - 'a');
    		}
    		dfs(st);
    		cout << mp.size() << endl;
    		return 0;
    	}
    	bool all_same = true;
    	for(int i = 2; i <= n; ++i) all_same &= (s[i] == s[1]);
    	if(all_same) { cout << 1 << endl; return 0; }
    	
    	dp[1][0][0][0] = dp[1][1][1][0] = dp[1][2][2][0] = 1;
    	for(int i = 2; i <= n; ++i) {
    		for(int j = 0; j < 3; ++j) {
    			for(int k = 0; k < 3; ++k) {
    				for(int l = 0; l <= 1; ++l) if(dp[i - 1][j][k][l]) {
    					for(int c = 0; c < 3; ++c) {
    						add(dp[i][c][(k + c) % 3][l | (c == j)], dp[i - 1][j][k][l]);
    					}
    				}
    			}
    		}
    	}
    	int sum = 0;
    	for(int i = 1; i <= n; ++i) sum += (s[i] - 'a');
    	sum %= 3;
    	int ans = 1;
    	for(int i = 2; i <= n; ++i) if(s[i] == s[i - 1]) { ans = 0; break; }
    	for(int j = 0; j < 3; ++j) add(ans, dp[n][j][sum][1]);
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    [一个64位操作系统的设计与实现] 3.1 Func_GetFATEntry疑惑
    【参考】 实现X86_64架构下的BootLoader(二)文件系统
    LBA和CHS转换(转)
    Grafana 重置admin密码
    linux-source: not found ubuntu执行脚本报错
    Hbase学习
    高并发理解
    Linux下安装Artemis
    SpringInAction 第八章 发送异步消息
    SpringInAction 六七章总结
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13565049.html
Copyright © 2011-2022 走看看