AtCoder 杂题

ABC213

E

不难转化为经典的 0-1 bfs。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

inline int read(){
   char ch=getchar(); int f=1;
   while(ch>'9'||ch<'0'){ if(ch=='-') f=-1; ch=getchar();}
   int sum=ch-'0';
   while((ch=getchar())>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch^48);
   return sum*f;
}
const int N=500+9,M=1024+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,m;
char s[N][N];
int dis[N][N]; 
deque<pair<int,int> > q;
void Mian(){
	cin>>n>>m; 
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			cin>>s[i][j];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			dis[i][j]=INF;
	dis[1][1]=0;
	q.push_front({1,1});
	while(!q.empty()){
		int x=q.front().fir,y=q.front().sec;
		q.pop_front();
		for(int tx=x-1;tx<=x+1;++tx){
			for(int ty=y-1;ty<=y+1;++ty){
				if(llabs(tx-x)+llabs(ty-y)==2) continue;
				if(tx<1 || ty<1 || tx>n || ty>m || 
				dis[tx][ty]<=dis[x][y] || s[tx][ty]=='#') continue; 
				dis[tx][ty]=dis[x][y];
				q.push_front({tx,ty});
			}
		} 
		for(int tx=x-2;tx<=x+2;++tx){
			for(int ty=y-2;ty<=y+2;++ty){
				if(llabs(tx-x)+llabs(ty-y)==4) continue;
				if(tx<1 || ty<1 || tx>n || ty>m || 
				dis[tx][ty]<=dis[x][y]+1) continue;
				dis[tx][ty]=dis[x][y]+1;
				q.push_back({tx,ty});
			}
		}
	} 
	cout<<dis[n][m]<<'\n';
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

F

考虑 SA。
建出后缀数组后,令 \(h_i=LCP(suf_{sa_i},suf_{sa_{i-1}})\),根据经典结论有 $$LCP(suf_{sa_i},suf_{sa_{j}})=min_{i<k\leq j} h_k$$
问题转化为给定一个数组,求以 \(i\) 为左/右端点的区间 min 之和。
使用单调栈不难做到 \(O(n)\) 递推。

总复杂度 \(O(n \log n)\),瓶颈在 SA。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

inline int read(){
   char ch=getchar(); int f=1;
   while(ch>'9'||ch<'0'){ if(ch=='-') f=-1; ch=getchar();}
   int sum=ch-'0';
   while((ch=getchar())>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch^48);
   return sum*f;
}
const int N=1e6+9,M=1024+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,h[N];
int pre[N],suf[N]; 
int rk[N],sa[N];
int tmp[N],bin[N],siz; 
char s[N]; 
void qsort(){
	for(int i=0;i<=siz;++i) bin[i]=0;
	for(int i=1;i<=n;++i) bin[rk[tmp[i]]]++;
	for(int i=1;i<=siz;++i) bin[i]+=bin[i-1];
	for(int i=n;i>=1;--i) sa[bin[rk[tmp[i]]]--]=tmp[i];
}
int calc(int x,int y,int d){
	while(x+d<=n && y+d<=n && s[x+d]==s[y+d]) ++d;
	return d;
}

int st[N],top;
void Mian(){
	scanf("%lld %s",&n,s+1);
	siz=max(n,'z'-'a'+1ll);
	for(int i=1;i<=n;++i)
		tmp[i]=i,rk[i]=s[i]-'a'+1;
	qsort(); 
	for(int j=1;j<=n;j<<=1){
		int tot=0;
		for(int i=n-j+1;i<=n;++i) tmp[++tot]=i;
		for(int i=1;i<=n;++i) 
			if(sa[i]-j>0) tmp[++tot]=sa[i]-j;
		qsort();
		swap(tmp,rk); 
		rk[sa[1]]=tot=1;
		for(int i=2;i<=n;++i){
			if(tmp[sa[i]]==tmp[sa[i-1]] &&
			   tmp[sa[i]+j]==tmp[sa[i-1]+j])
				rk[sa[i]]=tot;
			else rk[sa[i]]=++tot;
		} 
		if(tot==n) break;
	} 
	for(int i=1;i<=n;++i){
		h[rk[i]]=(rk[i]==1?0:
			  calc(i,sa[rk[i]-1],max(h[rk[i-1]]-1,0ll))); 
	}
	
	st[0]=0; pre[0]=0; top=0;
	for(int i=1;i<=n;++i){
		pre[i]=pre[i-1];
		while(top && h[i]<h[st[top]]){
			pre[i]-=h[st[top]]*(st[top]-st[top-1]);
			--top;
		}
		pre[i]+=h[i]*(i-st[top]);
		st[++top]=i;
	}
	st[0]=n+1; suf[n+1]=0; top=0;
	for(int i=n;i>=1;--i){
		suf[i]=suf[i+1];
		while(top && h[i]<h[st[top]]){
			suf[i]-=h[st[top]]*(st[top-1]-st[top]);
			--top;
		}
		suf[i]+=h[i]*(st[top]-i);
		st[++top]=i;
	}
	for(int i=1;i<=n;++i)
		cout<<pre[rk[i]]+suf[rk[i]+1]+n-i+1<<'\n';
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

G

\(f_S\) 表示点集为 \(S\) 的联通子图的个数,\(g_S\) 表示点集为 \(S\) 的子图的个数。
记点 \(k\) 的答案为 \(ans_k\)\(U\) 为点的全集。
由于要求 \(1,k\) 联通,考虑钦定 \(1,k\) 所在连通块,有:

\[ans_k=\sum_{ \left \{ 1,k \right \} \subset S \subset U} f_S \times g_{U\setminus S} \]

\(g_S\) 是好计算的,为 2 的边数次方。
考虑如何计算 \(f_S\),一个简单的想法是正难则反一下,转变为计数不连通子图的个数。
同样的,我们钦定一个点 \(u \in S\) 及其所在的连通块,同时需要存在与 \(u\) 不连通的点,故应枚举 \(S\) 的真子集,有:

\[f_S=g_S-\sum_{u \in T \subseteq S} f_T \times g_{S\setminus T} \]

实现中取 \(u=\operatorname{lowbit}(S)\) 即可,瓶颈在枚举子集,复杂度 \(O(3^n)\)

AC Code

ABC214

D

直接按边权从大往小做是边分治,整个过程逆过来从小往大做,删边改成加边,并查集就可以维护了。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

inline int read(){
   char ch=getchar(); int f=1;
   while(ch>'9'||ch<'0'){ if(ch=='-') f=-1; ch=getchar();}
   int sum=ch-'0';
   while((ch=getchar())>='0'&&ch<='9') sum=(sum<<3)+(sum<<1)+(ch^48);
   return sum*f;
}
const int N=1e5+9,M=(1<<17)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,fa[N],siz[N],ans;
int find(int u){
	return fa[u]==u?u:fa[u]=find(fa[u]);
}
struct edge{
	int u,v,w;
	bool operator<(const edge &x)const{
		return w<x.w;
	}
} e[N]; 
void Mian(){
	cin>>n; 
	for(int i=1;i<=n;++i){
		fa[i]=i; siz[i]=1;
	}
	for(int i=1;i<n;++i) cin>>e[i].u>>e[i].v>>e[i].w;
	sort(e+1,e+n);
	ans=0;
	for(int i=1;i<n;++i){
		e[i].u=find(e[i].u); e[i].v=find(e[i].v);
		ans+=siz[e[i].u]*siz[e[i].v]*e[i].w;
		siz[e[i].u]+=siz[e[i].v];
		fa[e[i].v]=e[i].u;
	} 
	cout<<ans;
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

E

发现不满足条件一定是存在某个区间满足这个区间包含的球数量大于区间长度,即:

\[\exists L,R \in [1,10^9],\sum [L \leq L_i \leq R_i \leq R] > R-L+1 \]

离散化后对右端点扫描线,维护区间长度减去球数量的全局最小值,只要这个全局最小值时刻非负就是合法的。
本质是 Hall 定理。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=4e5+9,M=(1<<17)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,L[N],R[N];
int tot,lsh[N];
int mi[N<<2],tag[N<<2],clr[N<<2];
void push_up(int u){
	mi[u]=min(mi[u<<1],mi[u<<1|1]);
}
void push_down(int u){
	if(tag[u]){
		mi[u<<1]+=tag[u];
		mi[u<<1|1]+=tag[u];
		tag[u<<1]+=tag[u];
		tag[u<<1|1]+=tag[u];
		tag[u]=0;
	}
	if(clr[u]){
		mi[u<<1]=mi[u<<1|1]=0;
		tag[u<<1]=tag[u<<1|1]=0;
		clr[u<<1]=clr[u<<1|1]=1;
		clr[u]=0;
	}
}
void modify(int u,int l,int r,int L,int R,int d){
	if(L<=l && r<=R){
		mi[u]+=d;
		tag[u]+=d;
		return ;
	}
	int mid=l+r>>1;
	push_down(u);
	if(L<=mid) modify(u<<1,l,mid,L,R,d);
	if(R>mid) modify(u<<1|1,mid+1,r,L,R,d);
	push_up(u);
}
vector<int> to[N];
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>L[i]>>R[i];
	for(int i=1;i<=n;++i) lsh[++tot]=L[i],lsh[++tot]=R[i];
	sort(lsh+1,lsh+tot+1);
	tot=unique(lsh+1,lsh+tot+1)-lsh-1;
	for(int i=1;i<=n;++i){
		L[i]=lower_bound(lsh+1,lsh+tot+1,L[i])-lsh;
		R[i]=lower_bound(lsh+1,lsh+tot+1,R[i])-lsh;
		to[R[i]].push_back(L[i]);
	}
	
	for(int i=1;i<=tot;++i){
		if(i>1) modify(1,1,tot,1,i-1,lsh[i]-lsh[i-1]);
		modify(1,1,tot,i,i,1);
		for(auto x:to[i])
			modify(1,1,tot,1,x,-1);
		if(mi[1]<0) return puts("No"),void();
	}
	puts("Yes");
} 
void Mianclr(){
	for(int i=1;i<=tot;++i) to[i].clear();
	clr[1]=1; mi[1]=tag[1]=0;
	tot=0; 
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T; cin>>T; 
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

Bonus:存在简单的贪心做法

F

好题。
首先考虑一个朴素的 dp,设 \(dp_i\) 为以 \(s_i\) 结尾的不同子串个数。直接做显然会算重。

为了不算重,对于每个不同的串,我们只在它第一次出现的位置统计。
则记 \(lst_i\) 表示 \(s_i\) 上一次出现的位置,那么 \([1,lst_i-2]\) 已经被 \(lst_i\) 计算过了,所以 \(dp_i\) 可以转移的区间为 \([lst_i-1,i-2]\),即:

\[dp_i=\sum_{j=lst_i-1}^{i-2} dp_j \]

前缀和优化即可。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=2e5+9,M=(1<<17)+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,tmp[199];
int dp[N],lst[N],pre[N];
string s; 
void Mian(){
	cin>>s; n=s.size();
	s=" "+s;
	for(int i=1;i<=n;++i){
		lst[i]=tmp[s[i]];
		tmp[s[i]]=i;
	} 
	for(int i=1;i<=n;++i){
		if(!lst[i]) dp[i]=1+(i>2?pre[i-2]:0);
		else dp[i]=(pre[i-2]-pre[lst[i]-2]+MOD)%MOD;
		pre[i]=(pre[i-1]+dp[i])%MOD;
	}
	cout<<pre[n];
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

G

想了一会直接算还是没啥前途,考虑容斥。

考虑 \(n\) 个条件形如 \(r_i\neq p_i\)\(r_i \neq q_i\),设 \(f_k\) 表示钦定恰好 \(k\) 个条件不满足的方案数,有:

\[ans=\sum_{i=0}^{n} (-1)^i \times f_k \times (n-i)! \]

接下来考虑如何计算 \(f_k\)。考虑把 \(p_i,q_i\) 连一条边,先把自环单独判掉,然后会发现图形成了若干个置换环。我们只需要对于每个置换环分别计数最后做一次背包合并即可。

对于一个有 \(n (n \geq 2)\) 个点的置换环,相当于我们要用边去匹配其相邻的点,直接做会有后效性。
那么我们往每条边中间再插入一个点,就变成了点到点的匹配。
梳理一下,问题变成了我们需要求在一个有 \(2n\) 个点的置换环上选出不相交的 \(k\) 组相邻点对的方案数。
如果问题在链上,这个就不难用组合数计算,考虑先在一个 \(2n-k\) 的链上随机选出 \(k\) 个点,然后往这 \(k\) 个点前面各塞一个点与其匹配,不难发现这样构造出的序列与直接选的序列是一一对应的,此时方案数为 \(\binom{2n-k}{k}\)
断环成链,考虑第一个点和最后一个点的点对选没选,由上文结论选了就是 \(\binom{2n-2-(k-1)}{k-1}\),没选就是 \(\binom{2n-k}{k}\),所以对于一个 \(n (n \geq 2)\) 个点的置换环,答案为:

\[\binom{2n-2-(k-1)}{k-1}+\binom{2n-k}{k} \]

自环的情况很简单,要么填要么不填,就一种情况。
复杂度 \(O(n\sum s_i)=O(n^2)\),很厉害,这个题就这样做完了。

Add:其实置换环方案计算可以直接 \(dp\),当然上组合意义也是很好的处理方式。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=3000+9,M=(1<<18)+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,p[N],q[N],cnt;
int fac[N<<1],inv[N<<1],f[N],ans;
int C(int n,int m){
	if(n<0 || m<0 || n<m) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD; 
}
vector<int> G[N]; 
bool vis[N];
void dfs(int u){
	if(vis[u]) return ;
	vis[u]=1; ++cnt;
	for(auto v:G[u]) dfs(v);
} 
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>p[i];
	for(int i=1;i<=n;++i) cin>>q[i];
	for(int i=1;i<=n;++i){
		G[p[i]].push_back(q[i]);
		G[q[i]].push_back(p[i]);
	} 
	
	fac[0]=1;
	for(int i=1;i<=n*2;++i) fac[i]=fac[i-1]*i%MOD;
	inv[n*2]=qpow(fac[n*2],MOD-2,MOD);
	for(int i=n*2;i>=1;--i) inv[i-1]=inv[i]*i%MOD;
	  
	f[0]=1; 
	for(int i=1;i<=n;++i){
		if(vis[i]) continue;
		cnt=0; dfs(i);
		if(cnt==1){
			for(int i=n;i>=1;--i) (f[i]+=f[i-1])%=MOD; 
		}else{
			for(int j=n;j>=1;--j){
				for(int k=1;k<=min(j,cnt);++k){
					int coef=C(cnt*2-k,k)+C(cnt*2-2-(k-1),k-1);
					f[j]=(f[j]+coef*f[j-k])%MOD;
				}
			}
		}
	}
	for(int i=0;i<=n;++i){
		int coef=(i&1?-1:1)*fac[n-i];
		ans=(ans+coef*f[i])%MOD;
	}
	cout<<(ans+MOD)%MOD;
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

ABC215

E

观察到不同的比赛类型最多只有 \(10\) 种,考虑状压。
\(f_{u,S}\) 表示当前比赛序列的种类状态为 \(S\),以种类 \(u\) 结尾的方案数。

再设 \(g_S=\sum_{i=0}^{9} f_{i,S}\),对于当前比赛 \(a_i\),就有:

\[\begin{cases} \forall a_i \in S,f_{a_i,S}\times 2\to f_{a_i,S},g_S+f_{a_i,S}\to g_S\\ \forall a_i \in S,f_{a_i,S}+g_{S\setminus a_i}\to f_{a_i,S},g_S+g_{S\setminus a_i}\to g_S \end{cases} \]

即接在同色后和开启一种新的颜色,边界条件 \(g_0=1\),统计答案时注意不能不参加比赛。

复杂度 \(O(n2^{10})\)

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=1000+9,M=(1<<10)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,a[N];
int f[M][19],g[M],ans; 
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i){
		char c; cin>>c;
		a[i]=c-'A';
	}
	g[0]=1; 
	for(int i=1;i<=n;++i){
		int pos=a[i]; 
		for(int S=0;S<(1<<10);++S) if((S>>pos)&1){
			(g[S]+=f[S][pos])%=MOD;
			(f[S][pos]*=2)%=MOD; 
		} 
		for(int S=(1<<10)-1;S;--S) if((S>>pos)&1){
			(g[S]+=g[S^(1<<pos)])%=MOD;
			(f[S][pos]+=g[S^(1<<pos)])%=MOD;
		}
	} 
	for(int S=1;S<(1<<10);++S) (ans+=g[S])%=MOD;
	cout<<ans;
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

F

简单题,显然要二分答案,check 的时候可以双指针算,就是让两个指针第一维距离大于等于 mid,对第二维算前缀 min 就行。

细节有点魔鬼,三个样例强度挺高,大部分错都能测出来。

复杂度 \(O(n \log V)\)

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=2e5+9,M=(1<<10)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int n,ans;
pair<int,int> a[N]; 
bool check(int mid){
	int mx=-INF,mi=INF;
	int i=1,j=1;
	while(j<=n){
		while(j<=n && a[j].fir-a[i].fir<mid){
			if(mx>-INF && llabs(a[j].sec-mx)>=mid) return 1;
			if(mi<INF && llabs(a[j].sec-mi)>=mid) return 1;
			++j; 
		}
		while(i<j && a[j].fir-a[i].fir>=mid){
			chkmax(mx,a[i].sec);
			chkmin(mi,a[i].sec);
			++i;
		}
		if(i>=j) return 1; 
	} return 0;
}
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i) 
		cin>>a[i].fir>>a[i].sec;
	sort(a+1,a+n+1);
	
	int L=0,R=(int)(1e9);
	ans=1;
	while(L<=R){
		int mid=(L+R)/2;
		if(check(mid)) L=(ans=mid)+1;
		else R=mid-1;
	} 
	cout<<ans;
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

Add:其实 check 的部分可以用线段树,码量大了点但是好调,所以其实会写得更快,充分揭示了 能不用双指针就不用双指针 路线的正确性。

G

神秘题。

首先考虑单个颜色的贡献,设当前颜色数量为 \(a_i\),则对于选 \(k\) 个糖果,不难得到贡献:$\cfrac{\binom{n}{k}-\binom{n-a_i}{k}}{\binom{n}{k}} $。

直接算是 \(O(n^2)\) 的,不太可行。

注意到此时 \(\sum a_i=n\),所以不同的 \(a_i\) 最多只有 \(\sqrt{n}\) 种。
分别对每种不同的 \(a_i\) 作统计就能做到 \(O(n\sqrt{n})\)

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=5e4+9,M=(1<<10)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

int fac[N],inv[N],cv[N];
int C(int n,int m){
	if(n<0 || m<0 || n<m) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
} 
int n,m,a[N],t[N];
int ans[N];
map<int,int> mp;
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i){
		int x; cin>>x;
		if(!mp[x]) mp[x]=++m;
		a[mp[x]]++;
	}
	for(int i=1;i<=m;++i) t[a[i]]++;
	
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD;
	inv[n]=qpow(fac[n],MOD-2,MOD);
	for(int i=n;i>=1;--i) inv[i-1]=inv[i]*i%MOD;
	for(int i=1;i<=n;++i) cv[i]=qpow(C(n,i),MOD-2,MOD);
	
	for(int i=1;i<=n;++i) if(t[i]){
		for(int k=1;k<=n;++k){
			int tmp=(C(n,k)-C(n-i,k)+MOD)%MOD;
			ans[k]=(ans[k]+tmp*cv[k]%MOD*t[i]%MOD)%MOD;
		}
	}
	for(int i=1;i<=n;++i) cout<<ans[i]<<'\n';
} 
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

H

感觉没有那么难。

先看第一问。把包菜的分配看成是一个二分图匹配问题,由 Hall 定理,知存在合法分配等价于对于任意的公司集合 \(S\),满足 \(\sum_{i \in N(S)} a_i \geq \sum_{i \in S} b_i\),此时答案显然为 \(\min_S(\sum_{i \in N(S)} a_i-\sum_{i \in S} b_i+1)\),枚举 \(S\) 复杂度爆完了,做一遍高维前缀和枚举 \(N(S)\) 即可。注意此时需要能匹配到公司,即 \(ans_1=\min_S(\sum_{i \in N(S)} a_i-\sum_{i \in S} b_i+1),\sum_{i \in S} b_i >0\)

对于第二问,先考虑什么样的集合 \(S\) 会有贡献,那就是存在它的一个超集 \(T \supseteq S\) 满足 \(\sum_{i \in N(T)} a_i-\sum_{i \in T} b_i+1=ans_1\)

接下来计数。一个简单的想法是直接把所有 \(\sum_{i \in N(S)} a_i-\sum_{i \in S} b_i+1=ans_1\)\(S\)\(\binom{\sum_{i \in N(S)} a_i}{ans_1}\) 直接累加,但是这个会算重。

考虑容斥,发现按照 \(\binom{\sum_{i \in N(S)} a_i}{ans_1}\) 算,对于任何集合 \(S\),其贡献在所有 \(T \supseteq S\) 中都计算了一遍,也就是相当于原贡献的高维前缀和,那么做一遍高维差分即可求得每个集合自己的贡献。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=1e4+9,M=(1<<20)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}

const int V=2e6+9;
int fac[V],inv[V];
int C(int n,int m){
	if(n<0 || m<0 || n<m) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int n,m,b[N],msk[N];
int A[M],B[M],ans,Ans[M];
bool chk[M]; 
void Mian(){
	cin>>n>>m;
	for(int i=0;i<n;++i) cin>>A[1<<i];
	for(int i=0;i<m;++i) cin>>b[i];
	for(int i=0;i<n;++i){
		for(int j=0;j<m;++j){
			int x; cin>>x;
			if(x) msk[j]|=(1<<i); 
		}
	}
	for(int i=0;i<m;++i) B[msk[i]]+=b[i];
	for(int i=0;i<n;++i){
		for(int S=0;S<(1<<n);++S) if((S>>i)&1){
			A[S]=A[S]+A[S^(1<<i)];
			B[S]=B[S]+B[S^(1<<i)];
		}
	}
	ans=INF;
	for(int S=0;S<(1<<n);++S) 
		if(B[S]) chkmin(ans,A[S]-B[S]+1);
	if(ans<=0) return cout<<0<<' '<<1,void();
	cout<<ans<<' ';
	
	fac[0]=1;
	for(int i=1;i<V;++i) fac[i]=fac[i-1]*i%MOD;
	inv[V-1]=qpow(fac[V-1],MOD-2,MOD);
	for(int i=V-1;i;--i) inv[i-1]=inv[i]*i%MOD;
	
	for(int S=0;S<(1<<n);++S)
		if(B[S] && A[S]-B[S]+1==ans) chk[S]=1;
	for(int i=0;i<n;++i)
		for(int S=(1<<n)-1;S;--S) 
			if(!((S>>i)&1)) chk[S]|=chk[S^(1<<i)];
	for(int S=0;S<(1<<n);++S) 
		if(chk[S]) Ans[S]=C(A[S],ans);
	for(int i=0;i<n;++i){
		for(int S=0;S<(1<<n);++S) if((S>>i)&1){
			Ans[S]=(Ans[S]-Ans[S^(1<<i)]+MOD)%MOD;
		}
	}
	ans=0;
	for(int S=0;S<(1<<n);++S)
		if(chk[S]) ans=(ans+Ans[S])%MOD;
	cout<<ans;
}
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

ARC125

B

哦这么神秘的。
\(x^2-y=t^2\),令 \(d=x-t\),有 \(d^2+2td=y\),计数的二元组变为 \((d+t,y)\)
此时直接枚举 \(d\) 就是 \(O(\sqrt{n})\) 的,满足条件的所有 \(t\) 不难直接用不等式求出,而且由于 \(d\) 不同简单理解一下也是不会重复计数的。

C

打个暴力手玩,不难发现答案形如 \(a_1b_1a_2b_2...a_{k-1}b_{k-1}+\) 一堆倒序排列的数。其中 \(b_k\) 为小于 \(a_k\) 且没有出现过的最小的数。

简单思考一下发现这个东西对完了然后就直接做。
神秘,,,

D

如果把在原序列中取得方案数唯一去掉就是前文的 abc214f。
现在有了这个限制,其实就是转移的时候对于任意的值,只能从其最后一次出现的位置转移。
套用 abc214f 的转移公式,加一个单点修改,用树状数组维护。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e17)
#define fir first
#define sec second

const int N=2e5+9,M=(1<<20)+9;
const int MOD=998244353,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=ret*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ret;
}
 
int n,a[N],dp[N]; 
int lst[N];
int f[N];
void modify(int u,int d){
	for(;u<=n;u+=lowbit(u))
		(f[u]+=d)%=MOD;
} 
int query(int u){
	int ret=0;
	for(;u;u-=lowbit(u))
		(ret+=f[u])%=MOD;
	return ret;
}
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i){
		if(!lst[a[i]]) dp[i]=(query(i-1)+1)%MOD;
		else{
			dp[i]=(query(i-1)-query(lst[a[i]]-1)+MOD)%MOD; 
			modify(lst[a[i]],-dp[lst[a[i]]]);
		}
		modify(i,dp[i]);
		lst[a[i]]=i;
	}
	cout<<(query(n)+MOD)%MOD; 
}
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}
posted @ 2025-12-30 13:03  Mi2uk1  阅读(4)  评论(0)    收藏  举报