2024ICPC南京站题解

文章目录

  • E - Left Shifting 3
  • J - Social Media
  • K - Strips
  • B - Birthday Gift
  • G - Binary Tree
  • C - Topology
  • I - Bingo

2024ICPC南京

E - Left Shifting 3

签到,可以两倍字符串直接判断

#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5010,M=4e5+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
void solve(){
	int n,k;
	cin>>n>>k;
	k=min(k,7ll);
	string s;
	cin>>s;
	s+=s;
	s=" "+s;
	vector<int> sum(2*n+1); 
	int ans=0;
	for(int i=1;i<=2*n;i++){
		sum[i]=sum[i-1];
		if(i>=7&&s.substr(i-6,7)=="nanjing")sum[i]++;
		if(i>=n&&i-n<=k)ans=max(ans,sum[i]-sum[i-n]);
	}
	cout<<ans<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}

J - Social Media

有三种情况:

  1. 要么和a,b都是好友
    • 直接计数
  2. 要么和其中一个是好友
    • 此时a,b的贡献记为have[a]++,have[b]++
  3. 要么都不是好友
    • 此时用一个map存这种情况的数量
      后面两种情况的一共的答案记为res,res要么为have数组的最大两个值,要么同时选择第三种情况的两个人产生的贡献
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5010,M=4e5+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
void solve(){
	int n,m,k;
	cin>>n>>m>>k;
	vector<int> vis(k+1),have(k+1),now(k+1);
	map<PII,int> cnt; 
 	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		vis[x]=1;
	}
	int ans=0;
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b;
		if(vis[a]&&vis[b])ans++;
		else if(vis[a])have[b]++;
		else if(vis[b])have[a]++;
		else{
			if(a<b)swap(a,b);
			if(a!=b)cnt[{a,b}]++;
			else have[a]++;
		}
	}
	for(auto &[p,c]:cnt){
		auto &[a,b]=p;
		now[a]=max(now[a],have[a]+have[b]+c);
		now[b]=max(now[b],have[a]+have[b]+c);
	}
	vector<int> tmp;
	int res=0;
	for(int i=1;i<=k;i++){
		if(!vis[i])res=max(res,now[i]),tmp.push_back(have[i]);
	}
	sort(tmp.begin(),tmp.end(),greater<int>());
	if(tmp.size()>=2)res=max(res,tmp[0]+tmp[1]);
	else if(tmp.size())res=max(res,tmp[0]);
	// cout<
	cout<<ans+res<<"\n";
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}

K - Strips

题意:

w个格子排成一排,n个红色,m个黑色,其余白色,可以放一条长度为k的板子,但
是不能覆盖黑色,问至少需要多少个板子覆盖所有红色,或说明不可能

思路:

直接贪心放,如果被黑色格子挡住了就需要左移,如果左移失败,则不可能

#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010;
const int INF=1e18;
// const int mod=998244353;
const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
void solve(){
    int n,m,k,w;
    cin>>n>>m>>k>>w;
    vector<int> a(n+1),b(m+1);
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++)cin>>b[i];
	sort(a.begin()+1,a.end());
	sort(b.begin()+1,b.end());
	vector<array<int,2>> seg;
	seg.push_back({0,0});
	b.push_back(w+1);
	for(int i=1;i<=n;i++){
		if(seg.back()[1]>=a[i])continue;
		int t=lower_bound(b.begin()+1,b.end(),a[i])-b.begin();
		int len;
		if(t==b.size())len=INF;
		else len=b[t]-a[i];
		seg.push_back({a[i],a[i]+k-1});
		if(len>=k)continue;
		int pp=k-len;
		while(true){
			int R=upper_bound(b.begin()+1,b.end(),seg.back()[0])-b.begin();
			R--;
			int x=seg.back()[0]-seg[seg.size()-2][1]-1;
			if(x>=pp){
				int left=seg.back()[0]-pp;
				if(b[R]<left){
					if(x==pp&&seg.size()!=2){
						int tmp=seg.back()[1]-pp;
						seg.pop_back();
						seg.back()[1]=tmp;
					}
					else seg.back()[0]-=pp,seg.back()[1]-=pp;
					break;
				}
				else{
					cout<<"-1\n";
					return;
				}
			}
			else{
				if(seg[seg.size()-2][1]==0){
					cout<<"-1\n";
					return;
				}
				if(b[R]<seg[seg.size()-2][1]+1){
					int len=seg.back()[1]-seg.back()[0]+1;
					seg.pop_back();
					seg.back()[1]+=len;
					pp-=x;
				}
				else{
					cout<<"-1\n";
					return;
				}
			}
		}
	}
	vector<int> ans;
	for(int i=1;i<seg.size();i++){
		int sz=seg[i][1]-seg[i][0]+1;
		for(int j=seg[i][0];j<=seg[i][1];j+=k)ans.push_back(j);
	}
	cout<<ans.size()<<"\n";
	for(auto it:ans)cout<<it<<" ";
	cout<<"\n";
}	
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}

B - Birthday Gift

题意:

只包含012的字符串,相邻的01可以抵消,可以将2变成0或1,询问最后的最短长度

思路:

  • 有后效性,因此贪心和dp都有问题
  • 可以发现相同字符的奇数位置个数和偶数位置个数在最后字符串必然会存在一个是0
  • 因此可以直接对每个字符的奇数和偶数位置个数处理
  • 更直观的做法也就是官方题解的做法是将偶数位全部反转,然后就变成了相同字符相消
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010;
const int INF=1e18;
// const int mod=998244353;
const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
void solve(){
	string s;
	cin>>s;
	vector<int> odd(3),even(3);
	for(int i=0;i<s.size();i++){
		i%2==0?even[s[i]-'0']++:odd[s[i]-'0']++;
	}
	for(int i=0;i<2;i++){
		int x=min(odd[i],even[i]);
		odd[i]-=x;
		even[i]-=x;
		if(odd[i]){
			int x=min(odd[i],even[2]);
			odd[i]-=x;
			even[2]-=x;
		}
		if(even[i]){
			int x=min(even[i],odd[2]);
			even[i]-=x;
			odd[2]-=x;
		}
	}
	int x=min(even[2],odd[2]);
	even[2]-=x,odd[2]-=x;
	int ans=0;
	for(int i=0;i<3;i++)ans+=odd[i]+even[i];
	cout<<ans<<"\n";
}	
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}

G - Binary Tree

题意:

  • 交互题,询问最多 ⌊ l o g n ⌋ \lfloor logn \rfloor logn次,找出二叉树上的一个点u
  • ? a b
    • 如果 d i s ( a , u ) < d i s ( b , u ) dis_{(a,u)}dis(a,u)<dis(b,u),返回0
    • 如果 d i s ( a , u ) = d i s ( b , u ) dis_{(a,u)}=dis_{(b,u)} dis(a,u)=dis(b,u),返回1
    • 如果 d i s ( a , u ) > d i s ( b , u ) dis_{(a,u)}>dis_{(b,u)} dis(a,u)>dis(b,u),返回2
  • ! u输出答案

思路:

  • 每次必须淘汰一半以上的点
  • 容易发现每次需要找重心
  • 四种情况:
    • 如果只剩重心一个点,那么直接输出
    • 如果与重心相邻的边只有一条,不难发现只有两个点的时候满足该情况,直接询 问? u v,如果为0则答案是u否则v
    • 如果有两条边,那么询问重心的两个儿子? v0 v1,如果为1说明答案为重心u,如果为0则将u打上标记把v0以上的部分全部剪掉,然后递归v0,否则递归v1
    • 如果有三条边,为02的情况和两条边一样,唯独为1时,此时剩余的树为u加上v3的子树大小,根据重心的性质:子树大小一定都小于等于一半,但是+1未必,v1 v2 v3不能随便选,需要选择较大子树的两个点作为v1 v2,保证最后一个子树v3的点+1小于等于一半(这样的点总是存在),如果乱选的话在特殊数据会增加答案询问。返回1则剪掉v1v2
  • 找重心可以直接套点分治的写法
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=3010;
const int INF=1e18;
// const int mod=998244353;
const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int ask(int x,int y){
	cout<<"? "<<x<<" "<<y<<endl;
	cin>>x;
	return x;
}
void claim(int x){
	cout<<"! "<<x<<endl;
}
void solve(){
	int n;
	cin>>n;
	vector<int> adj[n+1];
	for(int i=1;i<=n;i++){
		int x,y;
		cin>>x>>y;
		if(x!=0)adj[i].push_back(x),adj[x].push_back(i);
		if(y!=0)adj[i].push_back(y),adj[y].push_back(i);
	}
	vector<int> st(n+1);
	function<int(int,int)> get_size=[&](int u,int fa){
		if(st[u])return 0ll;
		int res=1;
		for(auto v:adj[u]){
			if(v==fa)continue;
			res+=get_size(v,u);
		}
		return res;
	};
	function<int(int,int,int,int&)> get_wc=[&](int u,int fa,int tot,int &wc){
		if(st[u])return 0ll;
		int sum=1,ms=0;
		for(auto v:adj[u]){
			if(v==fa)continue;
			int t=get_wc(v,u,tot,wc);
			ms=max(ms,t);
			sum+=t;
		}
		ms=max(ms,tot-sum);
		if(ms<=tot/2)wc=u;
		return sum;
	};
	bool g=0;
	function<void(int)> cal=[&](int u){
		if(st[u])return;
		if(g)return;
		get_wc(u,0,get_size(u,0),u);
		vector<int> tmp;
		for(auto v:adj[u]){
			if(!st[v])tmp.push_back(v);
		}
		if(tmp.size()==0){
			claim(u);
			g=1;
			return;
		} 
		if(tmp.size()==1){
			int x=ask(u,tmp[0]);
			if(x==0){
				claim(u);
				g=1;
				return;
			}
			else{
				claim(tmp[0]);
				g=1;
				return;
			}
		}
		if(tmp.size()==2){
			int x=ask(tmp[0],tmp[1]);
			if(x==1){
				claim(u);
				g=1;
				return;
			}
			st[u]=1;
			if(x==0){
				cal(tmp[0]);
				return;
			}
			cal(tmp[1]);
			return;
		}
		int sz[3];
		vector<array<int,2>> f;
		for(int i=0;i<3;i++)f.push_back({tmp[i],get_size(tmp[i],u)});
		sort(f.begin(),f.end(),[&](const array<int,2> &x,const array<int,2> &y){
			return x[1]>y[1];
		});
		for(int i=0;i<3;i++)tmp[i]=f[i][0];
		int x=ask(tmp[0],tmp[1]);
		if(x==1){
			st[tmp[0]]=st[tmp[1]]=1;
			cal(u);
			return;
		}
		else if(x==0){
			st[u]=1;
			cal(tmp[0]);
			return;
		}
		else{
			st[u]=1;
			cal(tmp[1]);
			return;
		}
	};
	cal(1);
}	
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    cin>>T;
    while(T--)solve();
    return 0;
}

C - Topology

题意:

对于每个点,计算树的拓扑序的个数满足 p i = i p_i=i pi=i

思路:

  • 首先需要知道一个结论:树的拓扑排序的个数为 n ! ∏ s i z i \frac{n!}{\prod siz_i} sizin!
  • 定义 f u f_u fu表示子树的拓扑序个数,可以在预处理size的时候同时求出来
  • d p i , j dp_{i,j} dpi,j表示 i i i在第 j j j位的个数,已经将子树 v v v之外的点全部排好的方案
  • d p u , x → d p v , y dp_{u,x} \rightarrow dp_{v,y} dpu,xdpv,y,设除了子树 v v v外的 u u u子树的拓扑序为 t m p tmp tmp,则需要在 n − x − s i z v n-x-siz_v nxsizv个空位放置 s i z u − s i z v − 1 siz_u-siz_v-1 sizusizv1个点,贡献为 t m p ⋅ C n − x − s i z v s i z u − s i z v − 1 tmp \cdot C_{n-x-siz_v}^{siz_u-siz_v-1} tmpCnxsizvsizusizv1
  • 直接枚举 x , y x,y x,y会超时,发现选择什么 x x x并不重要,因此可以用前缀和优化,得到以下式子: d p v , i = d p u , i − 1 ⋅ t m p ⋅ C n − ( i − 1 ) − s i z v s i z u − s i z v − 1 dp_{v,i}=dp_{u,i-1} \cdot tmp \cdot C_{n-(i-1)-siz_v}^{siz_u-siz_v-1} dpv,i=dpu,i1tmpCn(i1)sizvsizusizv1, d p v , i + = d p v , i − 1 dp_{v,i}+=dp_{v,i-1} dpv,i+=dpv,i1
  • 现在对于每个 d p i , i dp_{i,i} dpi,i要算上子树 i i i的贡献,首先是拓扑序个数 f i f_i fi,其次是选择位置,需要在 n − i n-i ni个位置,里面选 s i z i − 1 siz_i-1 sizi1个,因此答案为 d p i , i ⋅ f i ⋅ C n − i s i z i − 1 dp_{i,i} \cdot f_i \cdot C_{n-i}^{siz_i-1} dpi,ifiCnisizi1
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=5010,M=4e5+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int n,fac[N],invfac[N],siz[N],mul[N],f[N],dp[N][N];
vector<int> adj[N];
int qpow(int a,int b){
	int ans=1;
	for(;b;b>>=1){
		if(b&1)ans=ans*a%mod;
		a=a*a%mod;
	}
	return ans;
}
void init(){
	fac[0]=1;
	for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	invfac[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i>=0;i--)invfac[i]=invfac[i+1]*(i+1)%mod;
}
int C(int n,int m){
	if(n<m)return 0;
	return fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
void dfs(int u){
	siz[u]=f[u]=mul[u]=1;
	for(auto v:adj[u]){
		dfs(v);
		siz[u]+=siz[v];
		mul[u]=mul[u]*mul[v]%mod;
	}
	mul[u]=mul[u]*siz[u]%mod;
	f[u]=fac[siz[u]]*qpow(mul[u],mod-2)%mod;
}
void dfs1(int u){
	for(auto v:adj[u]){
		int tmp=f[u]*mul[v]%mod*siz[u]%mod*qpow(siz[u]-siz[v],mod-2)%mod*invfac[siz[u]]%mod*fac[siz[u]-siz[v]]%mod;
		// cout<
		for(int i=1;i<=n;i++){
			dp[v][i]=dp[u][i-1]*tmp%mod*C(n-i+1-siz[v],siz[u]-siz[v]-1)%mod;
			dp[v][i]+=dp[v][i-1];
			dp[v][i]%=mod;
		}
		dfs1(v);
	}
}
void solve(){
	cin>>n;
	init();
	for(int i=2;i<=n;i++){
		int x;
		cin>>x;
		adj[x].push_back(i);
	}
	dp[1][1]=1;
	dfs(1);
	dfs1(1);
	for(int i=1;i<=n;i++){
		dp[i][i]=dp[i][i]*f[i]%mod*C(n-i,siz[i]-1)%mod;
		cout<<dp[i][i]<<" \n"[i==n];
	}
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    int T=1;
    // cin>>T;
    while(T--)solve();
    return 0;
}

I - Bingo

题意:

给定一个长度为nm的整数序列,将其打乱之后按顺序填入
n ×m的网格中。定义bingo 整数为最小的整数k,满足存
在至少一行或者一列,其中所有的数都≤k。求出所有
(nm)! 种打乱方式的 bingo 整数之和,对 998244353 取模。

n m ≤ 200000 nm \leq 200000 nm200000

思路:

  • 定义一行一列的Bingo数为该行列的最大值
  • 则要求的即为所有行列的min值
  • min不好处理考虑min-max容斥: m i n ( S ) = ∑ T ⊂ S ( − 1 ) ∣ T ∣ − 1 m a x ( T ) min(S) =\sum_{T \sub S}(-1)^{|T|-1}max(T) min(S)=TS(1)T1max(T)
  • 此时max即为所有行列选中的最大值
  • 假设选择了x行y列,则一共选择了 c = m x + n y − x y c=mx+ny-xy c=mx+nyxy个元素,选择方案为 C n x C m y C_n^xC_m^y CnxCmy
  • 序列从小到大排列,假设 c c c个元素的最大值为 a i a_i ai,则前 i − 1 i-1 i1个元素要选择 c − 1 c-1 c1个,然后 c c c个元素全排列,剩下 n m − c nm-c nmc个元素全排列,最后的方案数即为 C i − 1 c − 1 c ! ( n m − c ) ! C_{i-1}^{c-1}c!(nm-c)! Ci1c1c!(nmc)!
  • 答案即为 a i ⋅ C n x C n y ⋅ C i − 1 c − 1 c ! ( n m − c ) ! a_i \cdot C_n^xC_n^y \cdot C_{i-1}^{c-1}c!(nm-c)! aiCnxCnyCi1c1c!(nmc)!
  • 暴力求是 O ( ( n m ) 2 ) O((nm)^2) O((nm)2),考虑将式子转化为卷积快速求解
  • a i ⋅ C i − 1 c − 1 c ! ( n m − c ) ! = a i ⋅ ( i − 1 ) ! ( c − 1 ) ! ( i − c ) ! ⋅ c ! ( n m − c ) ! = a i ( i − 1 ) ! ( i − c ) ! ⋅ c ( n m − c ) ! a_i \cdot C_{i-1}^{c-1}c!(nm-c)!=a_i \cdot \frac{(i-1)!}{(c-1)!(i-c)!} \cdot c!(nm-c)!=\frac{a_i(i-1)!}{(i-c)!} \cdot c(nm-c)! aiCi1c1c!(nmc)!=ai(c1)!(ic)!(i1)!c!(nmc)!=(ic)!ai(i1)!c(nmc)!
  • f i = a i ( i − 1 ) ! , g i = 1 ( n m − i ) ! , F x = ∑ i + j = x f i g j = ∑ i + j = x a i ( i − 1 ) ! ( n m − j ) ! = a i ( i − 1 ) ! ( n m − x + i ) ! f_i=a_i(i-1)!,g_i=\frac{1}{(nm-i)!},F_x=\sum_{i+j=x}f_ig_j=\sum_{i+j=x}\frac{a_i(i-1)!}{(nm-j)!}=\frac{a_i(i-1)!}{(nm-x+i)!} fi=ai(i1)!,gi=(nmi)!1,Fx=i+j=xfigj=i+j=x(nmj)!ai(i1)!=(nmx+i)!ai(i1)!
  • x = n m + c , F x = a i ( i − 1 ) ! ( n m − ( n m + c ) + i ) ! = a i ( i − 1 ) ! ( i − c ) ! x=nm+c,F_x=\frac{a_i(i-1)!}{(nm-(nm+c)+i)!}=\frac{a_i(i-1)!}{(i-c)!} x=nm+c,Fx=(nm(nm+c)+i)!ai(i1)!=(ic)!ai(i1)!
  • G c = a i ⋅ C i − 1 c − 1 c ! ( n m − c ) ! = c ⋅ ( n m − c ) ! ⋅ F n m + c G_c=a_i \cdot C_{i-1}^{c-1}c!(nm-c)!=c \cdot (nm-c)! \cdot F_{nm+c} Gc=aiCi1c1c!(nmc)!=c(nmc)!Fnm+c
  • 枚举x和y即可
namespace NTT{
    static constexpr int P=998244353;
    int rev[N],bit=0,tot,g=3,gi;
    int qpow(int a,int b){
        int ans=1;
        for(;b;b>>=1){
            if(b&1)ans=ans*a%P;
            a=a*a%P;
        }
        return ans;
    }
    int inv(int n){
        return qpow(n,P-2);
    }
    void ntt(int a[],int inv){
        for(int i=0;i<tot;i++){
            if(i<rev[i])swap(a[i],a[rev[i]]);
        }
        for(int mid=1;mid<tot;mid<<=1){
            int g1=qpow(inv==1?g:gi,(P-1)/(mid*2));
            for(int i=0;i<tot;i+=mid*2){
                int gk=1;
                for(int j=0;j<mid;j++,gk=gk*g1%P){
                    auto x=a[i+j],y=gk*a[i+j+mid]%P;
                    a[i+j]=(x+y)%P,a[i+j+mid]=(x-y+P)%P;
                }
            }
        }
        if(inv==-1){
            int tmp=NTT::inv(tot);
            for(int i=0;i<tot;i++)a[i]=a[i]*tmp%P;
        }
    }
    void init(int n){
        bit=0;
        while((1<<bit)<n)bit++;
        tot=1<<bit;
        for(int i=0;i<tot;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
        gi=inv(g);
    }
}
int f[N],g[N],fac[N],invfac[N];
void init(){
    for(int i=(fac[0]=1);i<N;i++)fac[i]=fac[i-1]*i%mod;
    invfac[N-1]=NTT::inv(fac[N-1]);
    for(int i=N-2;i>=0;i--)invfac[i]=invfac[i+1]*(i+1)%mod;
}
int C(int n,int m){
    if(n<m||m<0)return 0;
    return fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
void solve(){
	int n,m;
    cin>>n>>m;
    vector<int> a(n*m+1);
    for(int i=1;i<=n*m;i++)cin>>a[i];
    sort(a.begin(),a.end());
    for(int i=0;i<=n*m;i++){
        if(i==0)f[i]=0;
        else f[i]=a[i]*fac[i-1]%mod;
    }
    for(int i=0;i<=n*m;i++)g[i]=invfac[n*m-i];
    NTT::init(n*m*2+1);
    for(int i=n*m+1;i<NTT::tot;i++)f[i]=g[i]=0;
    NTT::ntt(f,1),NTT::ntt(g,1);
    for(int i=0;i<NTT::tot;i++)f[i]=f[i]*g[i]%mod;
    NTT::ntt(f,-1);
    for(int i=0;i<=n*m;i++)g[i]=i*fac[n*m-i]%mod*f[n*m+i]%mod;
    int ans=0;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            int t=C(n,i)*C(m,j)%mod*g[m*i+n*j-i*j]%mod;
            if((i+j)&1)ans=(ans+t)%mod;
            else ans=(ans-t+mod)%mod;
        }
    }
    cout<<ans<<"\n";
}   

你可能感兴趣的:(XCPC题解,算法)