牛客挑戰賽30題解
Orz Anson&Deadecho
A
枚舉\(b,c\),這樣\(a,d\)的限制也就確定了,二維數點即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=505;
int n,a[N],sum[N][N];long long ans;
int getsum(int x1,int x2,int y1,int y2){
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
int main(){
n=gi();
for(int i=1;i<=n;++i)a[i]=gi(),++sum[i][a[i]];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
for(int i=2;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(a[i]>a[j])
ans+=getsum(1,i-1,1,a[j]-1)*getsum(j+1,n,a[i]+1,n);
printf("%lld\n",ans);return 0;
}
B
好像被我強行水過去了?
考慮一個區間的貢獻\(seed^{(l-1)n+r}\),可以拆成\(seed^{(l-1)n}\times seed^r\),所以依然可以考慮枚舉每一個數作為中位數,將大於它的視作\(1\)小於它的視作\(-1\),找到所有包含這個數且和為\(0\)或\(\pm 1\)的區間(因為要考慮中位數有兩個的情況),將左端點與右端點的權值相乘即可得到答案。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N=10005;
const int mod=1e9+7;
int n,seed,a[N],p[N],P[N],tl[N<<1],tr[N<<1],ans;
inline void add(int &x,int y){x+=y;x>=mod?x-=mod:x;}
int main(){
n=gi();seed=gi();
for(int i=1;i<=n;++i)a[i]=gi();
p[0]=1;p[1]=seed;
for(int i=2;i<=n;++i)p[i]=1ll*p[i-1]*p[1]%mod;
P[0]=1;P[1]=p[n];
for(int i=2;i<=n;++i)P[i]=1ll*P[i-1]*P[1]%mod;
for(int i=1;i<=n;++i){
memset(tl,0,sizeof(tl));
memset(tr,0,sizeof(tr));
int res=0;
for(int j=i-1,s=0;j>=1;--j)s+=(a[j]>a[i]?1:-1),add(tl[s+N],P[j-1]);
for(int j=i+1,s=0;j<=n;++j)s+=(a[j]>a[i]?1:-1),add(tr[s+N],p[j]);
for(int x=min(i-1,n-i)+1,j=-x;j<=x;++j)
res=(res+((ll)tl[j+N]+tl[j+N]+tl[j-1+N]+tl[j+1+N])*tr[-j+N])%mod;
res=(res+((ll)tr[N]+tr[N]+tr[1+N]+tr[-1+N])*P[i-1])%mod;
res=(res+((ll)tl[N]+tl[N]+tl[1+N]+tl[-1+N])*p[i])%mod;
res=(res+(ll)P[i-1]*p[i]*2)%mod;
ans=(ans+(ll)res*a[i])%mod;
}
printf("%d\n",ans);
return 0;
}
C
考慮強制一個點最后一個刪,將其作為根節點,這樣刪點的順序就一定是從葉子到根,因而可以做一個簡單\(dp\)求出方案數,合並子樹時方案數乘上組合數即可。
現在要求以每個點為根(每個點最后一個刪)的答案。直接換根\(dp\)即可。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=1e5+5;
const int mod=998244353;
int n,inv[N],jc[N],jcn[N],sz[N],dp[N],ans;vector<int>E[N];
int fastpow(int a,int b){
int res=1;
while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
int C(int n,int m){return 1ll*jc[n]*jcn[m]%mod*jcn[n-m]%mod;}
void work(int u,int v){
sz[u]+=sz[v],dp[u]=1ll*dp[u]*dp[v]%mod*C(sz[u]-1,sz[v])%mod;
}
void rework(int u,int v){
dp[u]=1ll*dp[u]*fastpow(1ll*dp[v]*C(sz[u]-1,sz[v])%mod,mod-2)%mod,sz[u]-=sz[v];
}
void dfs1(int u,int f){
sz[u]=dp[u]=1;
for(int v:E[u])if(v!=f)dfs1(v,u),work(u,v);
}
void dfs2(int u,int f){
ans=(ans+dp[u])%mod;
for(int v:E[u])if(v!=f)rework(u,v),work(v,u),dfs2(v,u),rework(v,u),work(u,v);
}
int main(){
n=gi();inv[1]=jc[0]=jcn[0]=1;
for(int i=2;i<=n;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=1ll*jcn[i-1]*inv[i]%mod;
for(int i=1;i<n;++i){
int u=gi(),v=gi();
E[u].push_back(v);E[v].push_back(u);
}
dfs1(1,0);dfs2(1,0);printf("%d\n",ans);return 0;
}
D
不難發現答案式為\(\sum_{i=l}^r\binom{i+n-1}{n-1}\binom{s-i+m}{m}\)。
這是一個\(n+m\)次多項式然而沒辦法在\(O(n+m)\)的時間里算出前\(n+m\)項。
考慮其組合意義。相當於從\((0,0)\)點出發走到\((s,n+m)\),每步可以向上走或是向右走,且保證第\(n\)步向上走時橫坐標在\([l,r]\)內的方案數。
這個等價於求第\(l\)步向右走時縱坐標在\([0,n-1]\)的方案數減去第\(r+1\)步向右走時縱坐標在\([0,n-1]\)的方案數,而這些是可以做到\(O(n+m)\)計算的。
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N=2e7+5;
const int mod=998244353;
int n,m,s,l,r,inv[N],f[N],g[N];
int cal(int p){
if(p>s)return 0;int res=0;
//\sum_{i=0}^{n-1}\binom{i+p-1}{i}\binom{n+m-1+s-p}{n+m-i}
for(int i=f[0]=g[0]=1;i<=n+m;++i){
f[i]=1ll*f[i-1]*(s-p+i)%mod*inv[i]%mod;
g[i]=1ll*g[i-1]*(p-1+i)%mod*inv[i]%mod;
}
for(int i=0;i<n;++i)res=(res+1ll*f[n+m-i]*g[i])%mod;
return res;
}
int main(){
n=gi(),m=gi(),s=gi(),l=gi(),r=gi();inv[1]=1;
for(int i=2;i<N;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
printf("%d\n",(cal(l)-cal(r+1)+mod)%mod);return 0;
}
E
不難發現題目要求的就是仙人掌上每一條路徑的長度之和。
考慮分別計算每一條邊出現在了多少條路徑中。對仙人掌建圓方樹,然后這個方案數就可以用簡單換根\(dp\)求出。注意每經過一個方點(一個環)時方案數要乘\(2\)。
對於環邊,顯然環上的每一條邊的計算次數都是相同的,於是在\(dfs\)到方點的同時計算一下答案即可。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define pi pair<int,int>
#define mk make_pair
#define fi first
#define se second
const int N=2e5+5;
int n,tot,m,mod,fa[N],dep[N],dis[N],sum[N],mrk[N],sz[N],ans;
vector<pi>G1[N],G2[N];
void link(int u,int v,int w,vector<pi>*G){
G[u].push_back(mk(v,w));G[v].push_back(mk(u,w));
}
void dfs(int u,int f){
fa[u]=f;dep[u]=dep[f]+1;
for(pi x:G1[u])
if(x.fi!=f)
if(!dep[x.fi])dis[x.fi]=(dis[u]+x.se)%mod,dfs(x.fi,u);
else if(dep[x.fi]>dep[u]){
sum[++tot]=(x.se+dis[x.first]-dis[u]+mod)%mod;
for(int v=x.fi;v!=u;v=fa[v])link(tot,v,0,G2),mrk[v]=1;
link(tot,u,0,G2);
}
}
void work(int u,int v){
sz[u]=(sz[u]+(v<=n?1:2)*sz[v])%mod;
}
void undo(int u,int v){
sz[u]=(sz[u]-(v<=n?1:2)*sz[v]+mod+mod)%mod;
}
void dfs1(int u,int f){
sz[u]=(u<=n);
for(pi x:G2[u])if(x.fi!=f)dfs1(x.fi,u),work(u,x.fi);
}
void dfs2(int u,int f){
if(u>n){
int res=0,tmp=0;
for(pi x:G2[u])res=(res+1ll*tmp*sz[x.fi])%mod,tmp=(tmp+sz[x.fi])%mod;
ans=(ans+1ll*res*sum[u])%mod;
}
for(pi x:G2[u])
if(x.fi!=f){
undo(u,x.fi);ans=(ans+1ll*sz[u]*sz[x.fi]%mod*x.se)%mod;
work(x.fi,u);dfs2(x.fi,u);undo(x.fi,u);work(u,x.fi);
}
}
int main(){
n=tot=gi();m=gi();mod=gi();
for(int i=1,x,y,z;i<=m;++i)x=gi(),y=gi(),z=gi(),link(x,y,z,G1);
dfs(1,0);
for(int i=2;i<=n;++i)if(!mrk[i])for(pi x:G1[i])if(x.fi==fa[i])link(i,fa[i],x.se,G2);
dfs1(1,0);dfs2(1,0);printf("%d\n",ans);return 0;
}
F
考慮一下存在二次剩余的數\(x\)滿足一些什么樣的性質。
\(x^{\frac{p-1}{2}}\equiv1\mod p\)
而\(\mod p\)意義下恰有\(\frac{p-1}{2}\)個數存在二次剩余,即恰有\(\frac{p-1}{2}\)個數滿足上式。
根據某些代數高論我們可以得到
\(x^{\frac{p-1}{2}}-1=\prod_{i\mbox{存在二次剩余}}(x-i)\)
而我們要求的東西即為等式右邊的\(k\)次方與\(f(x)\)的\(\gcd\)。
可以先求出\((x^{\frac{p-1}{2}}-1)^k\mod f(x)\)后再與\(f(x)\)取\(\gcd\)。復雜度\(O(n^2\log p)\)。
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')w=0,ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define vi vector<int>
const int mod=998244353;
int inv(int a){
int res=1,b=mod-2;
while(b){if(b&1)res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
void print(vi a){
int n=a.size();printf("%d\n",n-1);
for(int i=0;i<n;++i)printf("%d ",a[i]);puts("");
}
vi mul(vi a,vi b){
int n=a.size(),m=b.size();vi c;c.resize(n+m-1);
for(int i=0;i<n;++i)
for(int j=0;j<m;++j)
c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
while(c.size()&&!c.back())c.pop_back();return c;
}
vi Mod(vi a,vi b){
int n=a.size(),m=b.size();
for(int i=n-1;i>=m-1;--i)
if(a[i]){
int t=1ll*(mod-a[i])*inv(b[m-1])%mod;
for(int j=0;j<m;++j)a[i-j]=(a[i-j]+1ll*b[m-1-j]*t)%mod;
}
while(a.size()&&!a.back())a.pop_back();return a;
}
vi fastpow(vi a,int b,vi c){
vi res;res.push_back(1);
while(b){
if(b&1)res=Mod(mul(res,a),c);
a=Mod(mul(a,a),c);b>>=1;
}
return res;
}
vi gcd(vi a,vi b){
if(!b.size())return a;
return gcd(b,Mod(a,b));
}
int main(){
int n=gi(),k=gi();vi f,g;
for(int i=0;i<=n;++i)f.push_back(gi());
g.push_back(0);g.push_back(1);
g=fastpow(g,mod-1>>1,f);
if(!g.size())g.push_back(mod-1);else g[0]=(g[0]+mod-1)%mod;
g=fastpow(g,k,f);g=gcd(f,g);
int t=inv(g[g.size()-1]);
for(int i=0;i<g.size();++i)g[i]=1ll*g[i]*t%mod;
print(g);return 0;
}