T1:一张n个点的无向图,求出经过每个点的最小环
n ≤ 300 n\le300 n≤300 m ≤ 40000 m\le40000 m≤40000
暴力是拆边然后跑dij,正解就是拆点
可以枚举每个点,做一个最短路树,然后枚举非树边更新答案就过了。。。
std是分治Floyd,就在每次分治的时候暴力向Floyd矩阵里插入一个半区内的所有点,然后递归另一个半区,直到只剩下一个节点的时候,就得到了删除这个点后图的Floyd矩阵
Code:
#include
#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(i≤n−k)
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+k−n]=(1−p)(E[i]+1)+p=(1−p)E[i]+1(n−k≤i≤n)
把这两个变换记作 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=0j−1S1(Ei)+∑i=jn−1S2(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=0kn−1S2(Ai)
涉及到一次函数的复合的变化,推一下式子可以发现是一个差比数列,直接求就好了
Code:
#include
#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 n≤200000
就是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
#define pb push_back
#define ll long long
#define db double
#define se second
#define fi first
#define poly vector
#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;
}