20190918CSP-S模拟题解

T1:一张n个点的无向图,求出经过每个点的最小环
n ≤ 300 n\le300 n300         m ≤ 40000 m\le40000 m40000

暴力是拆边然后跑dij,正解就是拆点
可以枚举每个点,做一个最短路树,然后枚举非树边更新答案就过了。。。
std是分治Floyd,就在每次分治的时候暴力向Floyd矩阵里插入一个半区内的所有点,然后递归另一个半区,直到只剩下一个节点的时候,就得到了删除这个点后图的Floyd矩阵

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=305;
ll d[10][N][N],e[N][N],ans[N];
int n,m,pt[N];
inline void add(ll tmp[N][N],int v){
	pt[v]=1;
	for(int i=1;i<=n;i++) if(pt[i])
		for(int j=1;j<=n;j++) if(pt[j]){
			tmp[v][i]=min(tmp[v][i],tmp[j][i]+e[v][j]);
			tmp[i][v]=tmp[v][i];
		}
	for(int i=1;i<=n;i++) if(pt[i])
		for(int j=1;j<=n;j++) if(pt[j])
			tmp[i][j]=min(tmp[i][j],tmp[i][v]+tmp[v][j]);
}
void solve(int k,int l,int r){
	if(l==r){
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++) ans[l]=min(ans[l],e[l][i]+e[l][j]+d[k][i][j]);
		return;
	}
	++k;int mid=l+r>>1;
	memcpy(d[k],d[k-1],sizeof(d[k-1]));
	for(int i=mid+1;i<=r;i++) add(d[k],i);
	solve(k,l,mid);
	for(int i=l;i<=r;i++) pt[i]=0;
	memcpy(d[k],d[k-1],sizeof(d[k-1]));
	for(int i=l;i<=mid;i++) add(d[k],i);
	solve(k,mid+1,r);
	for(int i=l;i<=r;i++) pt[i]=0;
}
int main(){
	memset(e,127/3,sizeof(e));
	memset(d,127/3,sizeof(d));
	memset(ans,127/3,sizeof(ans));
	n=read(),m=read();
	for(int x,y,i=1;i<=m;i++){
		x=read(),y=read();ll z=read();
		if(x==y) ans[x]=min(ans[x],z);
		ans[x]=min(ans[x],z+e[x][y]);
		ans[y]=min(ans[y],z+e[x][y]);
		e[x][y]=min(e[x][y],z);
		e[y][x]=min(e[y][x],z);
	}
	for(int i=1;i<=n;i++) d[0][i][i]=0;
	memset(pt,0,sizeof(pt));
	solve(0,1,n);
	for(int i=1;i<=n;i++) cout<<(ans[i]==ans[0]?-1:ans[i])<<" ";
	return 0;
}

T2:CF868G

显然有一个策略就是我们每次先找之前被找的次数最少的洞
那我们就按顺序找,每次找k个洞,如果找到最末端不够k个那就从头继续找
这样的话我们显然可以把 n , k n,k n,k除以它们的 g c d gcd gcd答案不变
然后可以得到两个转移( E [ i ] E[i] E[i]表示在第 i i i个洞找到的期望天数)
E [ i + k ] = E [ i ] + 1 ( i ≤ n − k ) E[i+k]=E[i]+1(i\le n-k) E[i+k]=E[i]+1(ink)
E [ i + k − n ] = ( 1 − p ) ( E [ i ] + 1 ) + p = ( 1 − p ) E [ i ] + 1 ( n − k ≤ i ≤ n ) E[i+k-n]=(1-p)(E[i]+1)+p=(1-p)E[i]+1(n-k\le i \le n) E[i+kn]=(1p)(E[i]+1)+p=(1p)E[i]+1(nkin)
把这两个变换记作 A , B A,B A,B,显然是一次函数型的
然后可以发现我们可以用前k个点来表示后面的所有点的 E E E值,相当于是列一个方程
那我们就只用求前k个点的答案,然后推一下可以发现每次是 ( k , n % k ) (k,n\%k) (k,n%k)的子问题
然后每次化成子问题的时候要推一下A,B的变化,网上很多博客里都有,这里就不讲了
如果把ans表示成以下形式
a n s = ∑ i = 0 j − 1 S 1 ( E i ) + ∑ i = j n − 1 S 2 ( E i ) ans=\sum_{i=0}^{j−1}S1(Ei)+\sum_{i=j}^{n-1}S2(Ei) ans=i=0j1S1(Ei)+i=jn1S2(Ei)
那么每次转移S1,S2的变化就是
S 1 = S 1 + ∑ i = 0 n k S 2 ( A i ) S1=S1+\sum_{i=0}^{\frac{n}{k}}S2(A^i) S1=S1+i=0knS2(Ai)
S 2 = S 1 + ∑ i = 0 n k − 1 S 2 ( A i ) S2=S1+\sum_{i=0}^{\frac{n}{k}-1}S2(A^i) S2=S1+i=0kn1S2(Ai)
涉及到一次函数的复合的变化,推一下式子可以发现是一个差比数列,直接求就好了

Code:

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define mod 1000000007
#define inv2 500000004
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void Dec(int &x,int y){x-=y;if(x<0) x+=mod;}
inline void Mul(int &x,int y){x=1ll*x*y%mod;}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
struct info{
	int k,b;
	info(){}
	info(int _k,int _b):k(_k),b(_b){}
	inline info operator + (const info &x)const{return info(add(k,x.k),add(b,x.b));}
	inline info operator * (const info &x)const{return info(mul(k,x.k),add(b,mul(k,x.b)));}
};
inline info iksm(info a,int n){
	info res=info(1,0);
	for(;n;n>>=1,a=a*a) if(n&1) res=a*res;
	return res;
}
inline info calc(info a,int n){
	if(!n) return info(0,0);
	if(a.k==1) return info(n,mul(mul(n,n+1),mul(inv2,a.b)));
	info tmp=iksm(a,n+1),res;Dec(tmp.k,a.k);
	res.k=mul(tmp.k,ksm(dec(a.k,1),mod-2));
	res.b=mul(dec(res.k,n),mul(ksm(dec(a.k,1),mod-2),a.b));
	return res;
}
inline int solve(int n,int k,info A,info B,info s1,info s0){
	if(!k) return mul(mul(A.b,ksm(dec(0,A.k-1),mod-2)),s0.k);
	info ns1=s1+(s0*calc(A,n/k)),ns0=s1+(s0*calc(A,n/k-1));
	info nA=B;int inv=ksm(nA.k,mod-2);
	nA.k=inv,nA.b=dec(0,mul(nA.b,inv));
	info nB=nA;inv=ksm(A.k,mod-2);
	A.k=inv,A.b=dec(0,mul(A.b,inv));
	nA=iksm(A,n/k-1)*nA,nB=iksm(A,n/k)*nB;
	int BB=add(mul(n%k,ns1.b),mul((k-n%k),ns0.b));
	ns1.b=ns0.b=0;
	return add(solve(k,n%k,nA,nB,ns1,ns0),BB);
}
signed main(){
	int t=read();
	while(t--){
		int n=read(),k=read(),p=read();
		int d=gcd(n,k);n/=d,k/=d;
		cout<<mul(solve(n,k,info(1,1),info(dec(0,p-1),1),info(1,0),info(1,0)),ksm(n,mod-2))<<"\n";
	}
	return 0;
}

T3:给出一个n维蛋糕的各维长度,在其表面刷上奶油,求有多少个超平面刷了 0 , 1...2 n 0,1...2n 0,1...2n面奶油
n ≤ 200000 n\le200000 n200000
就是THUPC2018蛋糕的变形
做过THUPC可以发现一个维度如果选会带来 2 2 2的贡献,不选会带来 a [ i ] − 2 a[i]-2 a[i]2的贡献,选会增加一面,弄成生成函数之后一个维度对应一个一次函数,常数项就是 a [ i ] − 2 a[i]-2 a[i]2,一次项是 2 2 2,然后所有维度乘起来之后第 i i i项的系数就对应被刷了 i i i面奶油的块数
注意这个维度为1的情况,就是贡献了两个面,把平方项的系数变为1即可
然后就是分治NTT了

Code:

#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define db double
#define se second
#define fi first
#define poly vector<int>
#define mod 998244353
#define g 3
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int N=2e6+5;
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void Dec(int &x,int y){x-=y;if(x<0) x+=mod;}
inline void Mul(int &x,int y){x=1ll*x*y%mod;}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
namespace Ntt{
	const int C=19;
	int *w[20],rev[N<<1];
	inline void init_rev(int n){for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));}
	inline void init_w(){
		for(int i=1;i<=C;i++) w[i]=new int[(1<<(i-1))];
		int wn=ksm(g,(mod-1)/(1<<C));
		w[C][0]=1;
		for(int i=1;i<(1<<(C-1));i++) w[C][i]=mul(w[C][i-1],wn);
		for(int i=C-1;i;i--)
			for(int j=0;j<(1<<(i-1));j++) w[i][j]=w[i+1][j<<1];
	}
	inline void ntt(poly &f,int n,int kd){
		for(int i=0;i<n;i++) if(i>rev[i]) swap(f[i],f[rev[i]]);
		for(int mid=1,l=1;mid<n;mid<<=1,l++)
			for(int i=0;i<n;i+=(mid<<1))
				for(int j=0,a0,a1;j<mid;j++){
					a0=f[i+j],a1=mul(f[i+j+mid],w[l][j]);
					f[i+j]=add(a0,a1);f[i+j+mid]=dec(a0,a1);
				}
		if(kd==-1){
			reverse(f.begin()+1,f.begin()+n);
			for(int inv=ksm(n,mod-2),i=0;i<n;i++) Mul(f[i],inv);
		}
	}
}
using namespace Ntt;
inline poly operator * (poly a,poly b){
	int m=a.size()+b.size()-1,n=1;
	while(n<m) n<<=1;
	init_rev(n);
	a.resize(n);ntt(a,n,1);
	b.resize(n);ntt(b,n,1);
	for(int i=0;i<n;i++) Mul(a[i],b[i]);
	ntt(a,n,-1);a.resize(m);return a;
}
int n,a[N];
inline poly solve(int l,int r){
	if(l==r){
		poly res;
		if(a[l]==1){res.resize(3);res[2]=1;}
		else if(a[l]>1) res.pb(a[l]-2),res.pb(2);
		return res;
	}
	int mid=l+r>>1;
	return solve(l,mid)*solve(mid+1,r);
}
inline void file(){freopen("cake.in","r",stdin);freopen("cake.out","w",stdout);}
int main(){init_w();
	int t=read();
	while(t--){
		n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		poly ans=solve(1,n);ans.resize((n<<1)+1);
		for(int i=0;i<=(n<<1);i++) cout<<ans[i]<<" ";
		cout<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值