• PKUWC/SC 做题笔记


    去年不知道干了些啥,什么省选/营题都没做。

    现在赶应该还来得及(?)

    「PKUWC2018」Minimax

    Done 2019.12.04 9:38:55

    线段树合并船新玩法???

    (O(n^2)) 很好想,先把叶子的权值离散化,然后 (dp[u][i]) 表示 (u) 的权值是 (i) 的概率。

    没事干了,上线段树合并。(???)

    线段树合并新玩法:对于线段树上的一个叶子,比它编号大的所有点在线段树上被拆成的区间应该是:对于递归到这个叶子路上的每个节点,如果是个左儿子,就算上它的兄弟(是个右儿子)。

    于是可以通过这个方法线段树合并。往左走的时候,左儿子需要乘上的数应该加上右儿子的和。具体见代码。

    时间复杂度 (O(nlog n))

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=300030,MOD=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,cnt[MAXN],ch[MAXN][2],val[MAXN],tmp[MAXN],tl,rt[MAXN],tot,ls[MAXN*50],rs[MAXN*50],sum[MAXN*50],mul[MAXN*50],s[MAXN],ans;
    inline int qpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=1ll*a*a%MOD) if(b&1) ans=1ll*ans*a%MOD;
    	return ans;
    }
    inline int newnode(){
    	int x=++tot;
    	ls[x]=rs[x]=sum[x]=0;
    	mul[x]=1;
    	return x;
    }
    inline void pushup(int x){
    	sum[x]=(sum[ls[x]]+sum[rs[x]])%MOD;
    }
    inline void setmul(int x,int v){
    	if(!x) return;
    	sum[x]=1ll*sum[x]*v%MOD;
    	mul[x]=1ll*mul[x]*v%MOD;
    }
    inline void pushdown(int x){
    	if(mul[x]!=1){
    		setmul(ls[x],mul[x]);
    		setmul(rs[x],mul[x]);
    		mul[x]=1;
    	}
    }
    void update(int &x,int l,int r,int p,int v){
    	if(!x) x=newnode();
    	if(l==r) return void(sum[x]=v);
    	int mid=(l+r)>>1;
    	pushdown(x);
    	if(mid>=p) update(ls[x],l,mid,p,v);
    	if(mid<p) update(rs[x],mid+1,r,p,v);
    	pushup(x);
    }
    int merge(int &x,int y,int l,int r,int xmul,int ymul,int v){
    	if(!x && !y) return 0;
    	if(!x){setmul(y,ymul);return y;}
    	if(!y){setmul(x,xmul);return x;}
    	pushdown(x);pushdown(y);
    	int mid=(l+r)>>1,lsx=sum[ls[x]],lsy=sum[ls[y]],rsx=sum[rs[x]],rsy=sum[rs[y]];
    	ls[x]=merge(ls[x],ls[y],l,mid,(xmul+1ll*rsy%MOD*(1-v+MOD))%MOD,(ymul+1ll*rsx%MOD*(1-v+MOD))%MOD,v);
    	rs[x]=merge(rs[x],rs[y],mid+1,r,(xmul+1ll*lsy%MOD*v)%MOD,(ymul+1ll*lsx%MOD*v)%MOD,v);
    	pushup(x);
    	return x;
    }
    void out(int x,int l,int r){
    	if(!x) return;
    	if(l==r) return void(s[l]=sum[x]);
    	int mid=(l+r)>>1;
    	pushdown(x);
    	out(ls[x],l,mid);
    	out(rs[x],mid+1,r);
    }
    void dfs(int u){
    	if(!cnt[u]) update(rt[u],1,tl,val[u],1);
    	else if(cnt[u]==1) dfs(ch[u][0]),rt[u]=rt[ch[u][0]];
    	else dfs(ch[u][0]),dfs(ch[u][1]),rt[u]=merge(rt[ch[u][0]],rt[ch[u][1]],1,tl,0,0,val[u]);
    }
    int main(){
    	read(n);
    	FOR(i,1,n){
    		int f;
    		read(f);
    		if(f) ch[f][cnt[f]++]=i;
    	}
    	FOR(i,1,n){
    		read(val[i]);
    		if(cnt[i]) val[i]=1ll*val[i]*qpow(10000,MOD-2)%MOD;
    		else tmp[++tl]=val[i];
    	}
    	sort(tmp+1,tmp+tl+1);
    	FOR(i,1,n) if(!cnt[i]) val[i]=lower_bound(tmp+1,tmp+tl+1,val[i])-tmp;
    	dfs(1);
    	out(rt[1],1,tl);
    	FOR(i,1,tl) ans=(ans+1ll*i*tmp[i]%MOD*s[i]%MOD*s[i])%MOD;
    	printf("%d
    ",ans);
    	return 0;
    }
    

    「PKUWC2018」Slay the Spire

    Done 2019.12.05 22:14:53

    一道 sb 题想了这么久,身败名裂……

    有个很显然的结论:先把强化牌从大到小能出就出(出撑死 (k-1) 张),然后就把攻击牌从大到小能出就出。

    不妨先把每种牌从大到小排序。

    枚举选了 (i) 张强化牌,(m-i) 张攻击牌。

    先求对于所有选强化牌的方案,最后倍数的和。

    (f_{i,j}) 表示前 (i) 张牌中选了 (j) 张的倍数和。

    然后求对于所有选攻击牌的方案,打出的牌的点数之和。发现打出的攻击牌的张数就是 (max(1,k-i))

    有个直接的想法是记录 (g_{i,j,k}) 表示前 (i) 张牌,选了 (j) 张牌,打出了 (k) 张的总和。

    于是我就卡在这个垃圾做法卡了好久。

    实际上可以枚举打出的牌中最小的是哪张。假设是 (j)

    记录 (g_{i,j,0/1}) 表示前 (i) 张牌,选了 (j) 张牌,第 (i) 张牌有没有选的总和。

    答案就是 (f_{n,i}g_{j,max(1,k-i),1}inom{n-j}{m-max(i+1,k)})。倍数,总和,和在更小的牌中随便选。

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

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=3333,MOD=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int T,n,m,k,a[MAXN],b[MAXN],fac[MAXN],inv[MAXN],invfac[MAXN],f[MAXN][MAXN],g[MAXN][MAXN],cnt[MAXN][MAXN];
    inline int C(int n,int m){
    	if(n<m || n<0 || m<0) return 0;
    	return 1ll*fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;
    }
    int main(){
    	read(T);
    	while(T--){
    		read(n);read(m);read(k);
    		FOR(i,1,n) read(a[i]);
    		FOR(i,1,n) read(b[i]);
    		sort(a+1,a+n+1,greater<int>());
    		sort(b+1,b+n+1,greater<int>());
    		fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    		FOR(i,2,2*n){
    			fac[i]=1ll*fac[i-1]*i%MOD;
    			inv[i]=MOD-1ll*(MOD/i)*inv[MOD%i]%MOD;
    			invfac[i]=1ll*invfac[i-1]*inv[i]%MOD;
    		}
    		f[0][0]=1;
    		FOR(i,1,n) FOR(j,0,i){
    			f[i][j]=f[i-1][j];
    			if(j) f[i][j]=(f[i][j]+1ll*f[i-1][j-1]*(j<=k-1?a[i]:1))%MOD;
    		}
    		FOR(i,1,n) FOR(j,0,i){
    			if(j) cnt[i][j]=g[i][j]=(g[i-1][j-1]+1ll*b[i]*C(i-1,j-1))%MOD;
    			g[i][j]=(g[i][j]+g[i-1][j])%MOD;
    		}
    		int ans=0;
    		FOR(i,0,m) FOR(j,0,n) ans=(ans+1ll*f[n][i]*cnt[j][max(1,k-i)]%MOD*C(n-j,m-max(i+1,k)))%MOD;
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    「PKUWC2018」斗地主

    咕了。

    「PKUWC2018」随机算法

    Done 2019.12.06 12:05:08

    神仙状压?

    (f_{S,i}) 表示 (S) 中的点要么在求出的独立集中,要么与独立集相邻,求出的独立集大小为 (i) 的方案数。这里的方案只考虑 (S) 中的点的位置,(S) 外的点不影响方案数。

    答案为 (f_{{1,2,dots,n},n})。如果第一维不是全集,就可以多放几个点。

    转移,随便选一个不在 (S) 中的点 (j)。预处理与 (j) 相邻的点集 (mask_j),那么 (f_{Scup{j}cup mask_j}) 会多 (f_S imes A_{n-|S|-1}^{|Scup{j}cup mask_j|-|S|-1})

    后面那个排列是把多限制的那些点((j) 本身除外,它肯定正好拼在目前第一个空位)塞到剩下的空位,考虑顺序。

    这是 (O(n^22^n)) 的。

    考虑为什么要记 (i) 这一位:因为我们最后用到的只有最大的独立集。

    但是有个常用的转移方法是:记录另一个 DP 数组表示这个集合的最大独立集。转移到这个集合时,如果是个更大的独立集,那前面的转移都废了。

    这样就是 (O(n2^n)) 了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1048576,mod=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,m,S[22],f[maxn],mx[maxn],fac[22],inv[22],invfac[22],sz[maxn];
    bool vis[maxn];
    inline int A(int n,int m){
    	if(n<0 || m<0 || n<m) return 0;
    	return 1ll*fac[n]*invfac[n-m]%mod;
    }
    int main(){
    	read(n);read(m);
    	FOR(i,1,m){
    		int u,v;
    		read(u);read(v);
    		u--;v--;
    		S[u]|=1<<v;S[v]|=1<<u; 
    	}
    	fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    	FOR(i,2,n){
    		fac[i]=1ll*fac[i-1]*i%mod;
    		inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
    		invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
    	}
    	FOR(i,1,(1<<n)-1) sz[i]=sz[i>>1]+(i&1);
    	f[0]=1;vis[0]=1;
    	FOR(i,0,(1<<n)-1){
    		if(!vis[i]) continue;
    		FOR(j,0,n-1) if(!((i>>j)&1)){
    			int to=i|(1<<j)|S[j];
    			vis[to]=true;
    			if(mx[to]<mx[i]+1) mx[to]=mx[i]+1,f[to]=0;
    			if(mx[to]==mx[i]+1) f[to]=(f[to]+1ll*f[i]*A(n-sz[i]-1,sz[to]-sz[i]-1))%mod;
    		}
    	}
    	printf("%d
    ",int(1ll*f[(1<<n)-1]*invfac[n]%mod));
    	return 0;
    }
    

    「PKUWC2018」猎人杀

    Done 2019.12.09 21:44:02

    一道比一道神……

    首先发现被杀死的猎人和杀手是哪个无关,可以就看成每次你自己按权随机。

    发现很难算,因为每次的分母不一样。

    那么考虑一种新的随机:在所有人中随机(包括死的),如果随机到一个死人就重新随机,直到随机到一个活人为止,然后把他毙了。

    这样是等价的。

    证明,考虑所有人的 (w) 之和为 (all),死人的 (w) 之和为 (kill),那么原来每个活人被随机到的概率是 (frac{w}{all-kill})

    枚举随机到多少次死人,现在每个活人被随机到的概率为 (sumlimits_{i=0}^{+infty}(frac{kill}{all})^ifrac{w}{all}=frac{w}{all} imesfrac{1}{1-frac{kill}{all}}=frac{w}{all-kill})

    现在考虑容斥(???),枚举 (S) 这个集合中的所有人都在 (1) 之前被杀死,剩下的人任意。

    枚举第一次随机 (1) 之前随机了几次(就是 (1) 什么时候被杀),(sumlimits_{i=0}^{+infty}(frac{all-w_1-sum(S)}{all})^ifrac{w_1}{all}=frac{w_1}{all} imesfrac{1}{1-frac{all-w_1-sum(S)}{all}}=frac{w_1}{w_1+sum(S)})

    所以答案就是 (sumlimits_{Ssubseteq {2,3,dots n}}(-1)^{|S|}frac{w_1}{w_1+sum(S)})

    考虑枚举 (sum(S)),计算所有和为 (i) 的集合 (S)((-1)^{|S|}) 之和。

    对每个除 (1) 以外的人构造多项式 (1-x^{w_i}),那么和为 (i) 的集合就是所有多项式乘积的第 (i) 项。

    求所有的乘积可以用分治乘起来。时间复杂度是 (O(sum ext{每个节点的多项式次数}log ext{每个节点的多项式次数}))

    (log) 放松一点变成 (logsum w_i),然后就是 (O((sum ext{每个节点的多项式次数})logsum w_i))

    每个叶子节点都会对它和它所有祖先的多项式次数产生贡献。这一共有 (O(log n)) 个,所以复杂度为 (O((sum w_i)log(sum w_i)log n))

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=266666,mod=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,w[maxn],sum[maxn],lim,l,rev[maxn],ans;
    vector<int> A[maxn];
    inline void init(int upr){
    	for(lim=1,l=0;lim<upr;lim<<=1,l++);
    	FOR(i,0,lim-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
    }
    inline int qpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
    	return ans; 
    }
    void NTT(vector<int> &A,int tp){
    	while(A.size()<lim) A.push_back(0);
    	FOR(i,0,lim-1) if(i<rev[i]) swap(A[i],A[rev[i]]);
    	for(int i=1;i<lim;i<<=1){
    		int Wn=qpow(3,mod-1+tp*(mod-1)/(i<<1));
    		for(int j=0;j<lim;j+=i<<1){
    			int w=1;
    			FOR(k,0,i-1){
    				int x=A[j+k],y=1ll*A[i+j+k]*w%mod;
    				A[j+k]=(x+y)%mod;A[i+j+k]=(x-y+mod)%mod;
    				w=1ll*w*Wn%mod;
    			}
    		}
    	}
    	if(tp==-1){
    		int linv=qpow(lim,mod-2);
    		FOR(i,0,lim-1) A[i]=1ll*A[i]*linv%mod;
    	}
    }
    void solve(int o,int l,int r){
    	if(l==r){
    		init(w[l]+1);
    		FOR(i,1,lim) A[o].push_back(0);
    		A[o][0]=1;
    		A[o][w[l]]=mod-1;
    		return;
    	}
    	int mid=(l+r)>>1;
    	solve(o<<1,l,mid);
    	solve(o<<1|1,mid+1,r);
    	init(sum[r]-sum[l-1]+1);
    	NTT(A[o<<1],1);NTT(A[o<<1|1],1);
    	FOR(i,0,lim-1) A[o].push_back(1ll*A[o<<1][i]*A[o<<1|1][i]%mod);
    	NTT(A[o],-1);
    }
    int main(){
    	read(n);
    	FOR(i,1,n) read(w[i]),sum[i]=sum[i-1]+w[i];
    	solve(1,2,n);
    	FOR(i,0,sum[n]-sum[1]) ans=(ans+1ll*w[1]*qpow((w[1]+i)%mod,mod-2)%mod*A[1][i])%mod;
    	printf("%d
    ",ans);
    	return 0;
    }
    

    「PKUWC2018」随机游走

    Done 2019.12.05 18:16:37

    咋就可做题了……咋就套路题了……

    显然的 (O(Q+8^nn^3)) 暴力高斯消元就略过了。

    对于“所有都选到至少一次”这种问题,先上个 min-max 容斥。(被教做人*1)

    然后就是要对每个点集都求出:从根开始,第一次走到点集中的点的期望步数。求完之后很容易能高维前缀和(FMT)(O(n2^n)) 地求出每个点集的最终答案。

    对每个点集暴力高斯消元,复杂度 (O(2^nn^3))

    然后发现是树上高斯消元,就可以直接 DP 了。(被教做人*2)

    (f_u) 表示从 (u) 开始第一次走到点集中的点的期望步数。再设 (a_u)(b_u) 满足 (f_u=a_uf_{fa_u}+b_u)。最终的期望步数为 (b_{rt})

    从意义出发,(f_u=frac{1}{deg_u}(f_{fa_u}+sumlimits_{vin son_u} f_v))

    (f_v=a_vf_u+b_v) 代入得:(f_u=frac{1}{deg_u}(f_{fa_u}+sumlimits_{vin son_u}(a_vf_u+b_v)))

    整理一波:(a_u=frac{1}{deg_u-sumlimits_{vin son_u}a_v},b_u=frac{deg_u+sumlimits_{vin son_u}b_v}{deg_u-sumlimits_{vin son_u}a_v})

    时间复杂度 (O(Q+n2^nlog))。这个 (log) 是求逆元,可以省掉,但是懒得写了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=262144,mod=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,q,rt,el,head[22],to[44],nxt[44],deg[22],a[22],b[22],s[MAXN],sz[MAXN];
    inline void add(int u,int v){
    	to[++el]=v;nxt[el]=head[u];head[u]=el;
    }
    inline int qpow(int a,int b){
    	int ans=1;
    	for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
    	return ans;
    }
    void dfs(int S,int u,int f){
    	int asum=0,bsum=0;
    	for(int i=head[u];i;i=nxt[i]){
    		int v=to[i];
    		if(v==f) continue;
    		dfs(S,v,u);
    		asum=(asum+a[v])%mod;
    		bsum=(bsum+b[v])%mod;
    	}
    	if((S>>u)&1) a[u]=b[u]=0;
    	else{
    		int inv=qpow((deg[u]-asum+mod)%mod,mod-2);
    		a[u]=inv,b[u]=1ll*inv*(deg[u]+bsum)%mod;
    	}
    }
    int main(){
    	read(n);read(q);read(rt);rt--;
    	FOR(i,1,n-1){
    		int u,v;
    		read(u);read(v);
    		u--;v--;
    		add(u,v);add(v,u);
    		deg[u]++;deg[v]++; 
    	}
    	FOR(i,1,(1<<n)-1){
    		MEM(a,0);MEM(b,0);
    		dfs(i,rt,-1);
    		s[i]=b[rt];
    		sz[i]=sz[i>>1]+(i&1);
    		if(sz[i]%2==0) s[i]=(mod-s[i])%mod;
    	}
    	for(int i=1;i<1<<n;i<<=1)
    		for(int j=0;j<1<<n;j+=i<<1)
    			FOR(k,0,i-1) s[i+j+k]=(s[i+j+k]+s[j+k])%mod;
    	while(q--){
    		int k,S=0;
    		read(k);
    		while(k--){
    			int x;
    			read(x);
    			S|=1<<(x-1);
    		}
    		printf("%d
    ",s[S]);
    	}
    	return 0;
    }
    

    「PKUSC2018」真实排名

    Done 2019.12.04 15:07:54

    在我说底下「神仙的游戏」是全场最可做题之后就被喷了,滚过来看这题……

    然而感觉 lower_bound 和 upper_bound 的细节在考场上也八成会暴毙……

    不过这题想起来还是很简单的。

    直接分类:

    • 特判 (a_i=0),此时任选即可,答案为 (inom{n}{k})
    • 如果 (i) 没有翻倍,那么如果 ([lfloorfrac{a_i+1}{2} floor,a_i-1]) 这里面有人翻倍了,他就绝杀 (i) 了。所以在剩下的人中再选 (k) 个。
    • 如果 (i) 翻倍了,那么他就多踩了 ([a_i,2a_i-1]) 这些人。所以这些人也必须翻倍。剩下的可以任选。

    时间复杂度 (O(nlog n)),瓶颈在排序和二分。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=100010,mod=998244353;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,k,a[MAXN],b[MAXN],fac[MAXN],inv[MAXN],invfac[MAXN];
    inline int cnt(int x){
    	int p=upper_bound(b+1,b+n+1,x)-b;
    	if(p>n || b[p]>x) p--;
    	return p;
    }
    inline int cnt(int l,int r){return cnt(r)-cnt(l-1);}
    inline int C(int n,int m){
    	if(n<m || n<0 || m<0) return 0;
    	return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
    }
    int main(){
    	read(n);read(k);
    	FOR(i,1,n) read(a[i]),b[i]=a[i];
    	sort(b+1,b+n+1);
    	fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    	FOR(i,2,n){
    		fac[i]=1ll*fac[i-1]*i%mod;
    		inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
    		invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
    	}
    	FOR(i,1,n){
    		if(!a[i]){
    			printf("%d
    ",C(n,k));
    			continue;
    		}
    		int s1=C(cnt((a[i]-1)/2)+cnt(a[i],1e9)-1,k);
    		int s2=C(cnt(a[i]-1)+cnt(2*a[i],1e9),k-cnt(a[i],2*a[i]-1));
    		printf("%d
    ",(s1+s2)%mod);
    	}
    	return 0;
    }
    

    「PKUSC2018」最大前缀和

    Done 2019.05.24 22:24:28

    神仙状压。

    之前做过

    「PKUSC2018」主斗地

    咕了。

    「PKUSC2018」星际穿越

    Done 2019.12.09 9:45:49

    神仙套路?

    (sum(x,y)) 表示从 (y) 跳到 (x)(y) 中每一个点的距离之和。答案为 (sum(l,x)-sum(r+1,x))

    (y) 第一步能跳到的最小点是 (l_y),最大点是满足 (l_kle y) 的最大的 (k)

    那么第二步能跳到的最小点是 (minlimits_{i=l_y}^nl_i)

    为什么上界是 (n),不需要考虑 (l_ile y) 这个条件?因为对于 (l_i>y)(i),肯定不是最优的,比 (y) 都要劣。

    发现从第二步开始,就不会往右跳了。因为往右跳可能更优肯定是因为它的 (l) 比当前所在点的 (l) 更小。但是这样的话,它的 (l) 肯定比 (y) 小,第一步往右跳就能跳到这个点。

    发现如果能跳到的最小点是 (a),那么 ([a,y)) 都能在这么多步以内跳到。

    所以如果第 (i) 步(注意 (i e 0),因为一开始不能在 (y) 右边)能跳到的最小点是 (a),那么第 (i+1) 步能跳到的最小点是 (minlimits_{i=a}^nl_i)

    没事干了,上倍增。(???)

    (f_{i,j}) 表示从 (i) 开始跳 (2^j) 步最小编号的点(注意这里为了方便,假设 (i) 右边的点一开始就能用),(g_{i,j}) 表示从 (i) 跳到 (f_{i,j})(i) 的距离之和。

    那么有 (f_{i,0})(l_i) 的后缀最小值,(g_{i,0}=i-f_{i,0})

    转移,(f_{i,j}=f_{f_{i,j-1},j-1},g_{i,j}=g_{i,j-1}+g_{f_{i,j-1},j-1}+2^{j-1}(f_{i,j-1}-f_{i,j}))(g) 的转移最后一部分是因为 ([f_{i,j},f_{i,j-1})) 在跳到 (f_{i,j-1}) 后(这部分的和是 (g_{f_{i,j-1},j-1}))还要再花 (2^{j-1}) 才能到 (i)

    倍增的过程,从 (y) 开始跳,特殊处理第一步(上面的 (f) 没有这个特殊情况),然后从大到小看跳了 (2^i) 步是否仍然不在 (x) 左边。同时记一下贡献。具体过程与上面类似,详见代码。

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

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=333333;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    int n,lt,l[maxn],q,f[20][maxn];
    ll g[20][maxn];
    ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
    ll sum(int x,int y){
    	if(l[y]<=x) return y-x;
    	ll s=y-l[y];
    	int at=l[y],cnt=1;
    	ROF(i,lt,0) if(f[i][at]>=x){
    		s+=1ll*cnt*(at-f[i][at])+g[i][at];
    		at=f[i][at];
    		cnt+=1<<i;
    	}
    	if(at>x) s+=1ll*cnt*(at-x)+at-x;
    	return s;
    }
    int main(){
    	read(n);
    	FOR(i,2,n) read(l[i]);
    	lt=log(n)/log(2);
    	f[0][n]=l[n];
    	ROF(i,n-1,1) f[0][i]=min(f[0][i+1],l[i]);
    	FOR(i,1,n) g[0][i]=i-f[0][i];
    	FOR(j,1,lt) FOR(i,1,n){
    		f[j][i]=f[j-1][f[j-1][i]];
    		g[j][i]=g[j-1][i]+g[j-1][f[j-1][i]]+(1ll<<(j-1))*(f[j-1][i]-f[j][i]);
    	}
    	read(q);
    	while(q--){
    		int l,r,x;
    		read(l);read(r);read(x);
    		ll s=sum(l,x)-sum(r+1,x),cnt=r-l+1,g=gcd(s,cnt);
    		s/=g;cnt/=g;
    		printf("%lld/%lld
    ",s,cnt);
    	}
    	return 0;
    }
    

    「PKUSC2018」神仙的游戏

    Done 2019.12.04 12:04:00

    或成最可做题???

    不知道大家为什么说 minimax 最可做,这题我明明很快就切了虽然还是先想了个假做法

    考虑有长度为 (i) 的 border 就是有长度为 (n-i) 的循环节。

    我们判断 (i) 合不合法的时候,发现对于所有模 (n-i) 相同的位,上面不能同时有 0 和 1。

    转换一下,枚举一个是 0 的位置 (x),是 1 的位置 (y),那么 (|x-y|) 及其约数都不能作为循环节的长度。

    不妨先求出所有 (|x-y|) 的可能值,最后枚举倍数。

    那么就能随便 FFT 解决了。时间复杂度 (O(nlog n))

    FFT 太慢了……NTT 会快很多,但是没写。

    别像我第一次一样沙雕,只需要一次 FFT。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAXN=1111111;
    const double pi=3.14159265358;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    template<typename T>
    inline void read(T &x){
    	x=0;
    	char ch=getchar();bool f=false;
    	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    	if(f) x=-x;
    }
    struct comp{
    	double x,y;
    	comp(double xx=0,double yy=0):x(xx),y(yy){}
    	comp operator+(const comp &c)const{return comp(x+c.x,y+c.y);}
    	comp operator-(const comp &c)const{return comp(x-c.x,y-c.y);}
    	comp operator*(const comp &c)const{return comp(x*c.x-y*c.y,x*c.y+y*c.x);}
    }A[MAXN],B[MAXN];
    int n,a[MAXN],b[MAXN],lim,l,rev[MAXN];
    ll ans;
    double res[MAXN];
    char s[MAXN];
    void fft(comp *A,int tp){
    	FOR(i,0,lim-1) if(i<rev[i]) swap(A[i],A[rev[i]]);
    	for(int i=1;i<lim;i<<=1){
    		comp Wn(cos(pi/i),tp*sin(pi/i));
    		for(int j=0;j<lim;j+=i<<1){
    			comp w(1,0);
    			FOR(k,0,i-1){
    				comp x=A[j+k],y=A[i+j+k]*w;
    				A[j+k]=x+y;A[i+j+k]=x-y;
    				w=w*Wn;
    			}
    		}
    	}
    	if(tp==-1) FOR(i,0,lim-1) A[i]=comp(A[i].x/lim,0);
    }
    int main(){
    	scanf("%s",s);
    	n=strlen(s);
    	for(lim=1,l=0;lim<n*2;lim<<=1,l++);
    	FOR(i,0,lim-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
    	FOR(i,0,n-1){
    		if(s[i]=='0') A[i]=comp(1,0);
    		if(s[i]=='1') B[n-1-i]=comp(1,0);
    	}
    	fft(A,1);fft(B,1);
    	FOR(i,0,lim-1) A[i]=A[i]*B[i];
    	fft(A,-1);
    	FOR(i,0,n-1) res[i]+=A[n-1+i].x+A[n-1-i].x;
    	ROF(i,n,1) FOR(j,2,n/i) res[i]+=res[i*j];
    	FOR(i,1,n) if(fabs(res[n-i])<1e-3) ans^=1ll*i*i;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    「PKUSC2018」PKUSC

    咕了。

    可以转换成对于每个敌人,落在多边形内部的概率。

    多边形旋转和敌人旋转没啥区别,转换成敌人转一圈落在多边形内部的概率。

    轨迹是个圆,这个圆上有一堆圆弧在多边形内部。转换成求这些圆弧所对圆心角的和。

    这个大概求出所有交点,极角序排序搞一搞。

    说的容易,但是调了一天了,现在还咕着。

  • 相关阅读:
    页式管理
    Chord算法(原理)
    php实现反转链表(链表题一定记得画图)(指向链表节点的指针本质就是一个记录地址的变量)($p->next表示的是取p节点的next域里面的数值,next只是p的一个属性)
    js进阶ajax的XMLHttpRequest对象的status和statustext属性(如果ajax和php联合使用的话:open连接服务器的第二个参数文件路径改成请求php的url即可)
    js进阶ajax基本用法(创建对象,连接服务器,发送请求,获取服务器传过来的数据)
    js进阶课程ajax简介(ajax是浏览器来实现的)
    php面试题四
    heredoc(实现模板与代码的分离)
    如何查看计算机所连接的打印机
    php面试题三
  • 原文地址:https://www.cnblogs.com/1000Suns/p/11979814.html
Copyright © 2020-2023  润新知