good-problems 11

1.D. Paths on the Tree
题意:

题解:
首先根据题目的限制,我们从上往下考虑
第一个点一定是k次经过,那么它的儿子们要不是ksz[1]次经过,要不就是ksz[1]
由此可以知道每个点的经过次数,一定是不变的两个可能kk+1
为什么不变,是固定的两个可能?难道祖先一直多取一个,就是使当前节点的可能更大?
考虑性质,同父亲的两个点的经过次数只差最多只有1,那么+1往下传递的影响可看作没有,所以每个点取的可能只有固定的2种

知道了这个性质就好做了
dp[u][0/1] 表示u节点经过k或(k+1)次的最大值
考虑如何转移,
我们先求出所有u的儿子v的dp值dp[v][0]dp[v][1]
如果u平均分k给u的儿子后还多出一些经过次数,就把这些多余分配次数给delt=dp[v][1]dp[v][0]大的儿子
先取delt大的儿子的dp[v][1],再去其他的dp[v][0]
dp[u][0]dp[u][1]的转移略有不同,因为dp[u][1]的k多1

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("Yes");return;}
#define NO {puts("No");return ;}
using namespace std;
const int maxn=7e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int tot,head[maxn],nx[maxn],to[maxn],sz[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
ll dp[maxn][2],s[maxn];
void dfs(int x,int fa,ll k){
	dp[x][0]=s[x]*k;
	dp[x][1]=s[x]*(k+1);
	if(sz[x]==0)return ;
	ll k0=k/sz[x],yu=k%sz[x];
	for(int i=head[x];i;i=nx[i]){
		int v=to[i];if(v==fa)continue;
		dfs(v,x,k0); 
	}
	vector<pair<ll,int> >f;
	for(int i=head[x];i;i=nx[i]){
		int v=to[i];if(v==fa)continue;
		f.pb(mp(dp[v][1]-dp[v][0],v));
	}
	sort(f.begin(),f.end());
	for(int i=f.size()-1;i>=0;i--){
		if(yu>0)dp[x][0]+=dp[f[i].se][1];
		else dp[x][0]+=dp[f[i].se][0];
		
		if(yu>=0)dp[x][1]+=dp[f[i].se][1];//dp[x][1]多一个选择 
		else dp[x][1]+=dp[f[i].se][0];
		yu--;
	}
	return ;
}
void solve(){
	int n=read(),k=read();
	tot=0;for(int i=1;i<=n;i++)head[i]=dp[i][0]=dp[i][1]=sz[i]=0;
	for(int i=2;i<=n;i++){
		int x=read();sz[x]++;
		add(x,i);add(i,x);
	}
	for(int i=1;i<=n;i++)s[i]=read();
	dfs(1,1,k);cout<<dp[1][0]<<endl;
	return ;
}

int main(){
	int t=read();
	while(t--)solve();
    return 0;
}

2.D. Factorial Divisibility

map来统计每一个数出现的次数,我们发现一个数字出现的次数可以累加到这个数的上一位。比如出现了5个4,那么这5个4就可以形成一个5的倍数,相当于多出现了一个5。我们从低位往高位累加,判断mp[i] % (i + 1)是否等于0,如果不等于0一定无解。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

void solve(){
	int n=read(),k=read();
	vector<int>a(n+1);
	map<int,int>mm;
	for(int i=1;i<=n;i++){
		a[i]=read();
		mm[a[i]]++;
	}
	for(int i=1;i<k;i++){
		if(mm[i]%(i+1))NO;
		mm[i+1]+=mm[i]/(i+1);
	}
	YES;
	return ;
}

int main(){
	int t=1;
	while(t--)solve(); 
    return 0;
}

3.E. Wish I Knew How to Sort

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

ll power(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1)ans=ans*x%MOD;
		y>>=1;x=x*x%MOD;
	}
	return ans;
}

void solve(){
	int n=read();
	vector<int>a(n+1);
	int tot=0;
	for(int i=1;i<=n;i++)a[i]=read(),tot+=(a[i]==0);
	vector<ll>dp(n+1); 
	int k=0;
	for(int i=1;i<=tot;i++)if(a[i])k++;
	dp[0]=0;
	for(int i=1;i<=k;i++){
		dp[i]=n*(n-1)%MOD*power(2,MOD-2)%MOD*power(i*i,MOD-2)%MOD+dp[i-1];
		dp[i]%=MOD;
	}
	dp[k]=(dp[k]%MOD+MOD)%MOD;
	cout<<dp[k]<<endl;
	return ;
}

signed main(){
	int t=read();
	while(t--)solve(); 
    return 0;
}

4.B. The Great Wall
题意:给出一个长度为n的数组 a ,将数组分成 k 段,每段的价值为最大值减去最小值。数组的价值为每段价值之和。
求k = 1 2 3 4 ··n 时,数组的最大价值。
题解:
可以将每段价值转换成选择两个数相减,使得最后总和最大,那么最优必是最大值减去最小值


dp[i][j][0]== 前i个数分成j段,第j段+-都没
dp[i][j][1]== 前i个数分成j段,第j段+有
dp[i][j][2]== 前i个数分成j段,第j段-有
dp[i][j][3]== 前i个数分成j段,第j段+-都有

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=3e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int dp[2][10002][4];
/*
	dp[i][j][0]== 前i个数分成j段,第j段+-都没 
	dp[i][j][1]== 前i个数分成j段,第j段+有
	dp[i][j][2]== 前i个数分成j段,第j段-有 
	dp[i][j][3]== 前i个数分成j段,第j段+-都有 
*/
signed main(){
	int n=read();
	vector<int>a(n+1);
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=0;i<=1;i++)for(int j=0;j<=n+1;j++)for(int k=0;k<=3;k++)dp[i][j][k]=-inf;
	dp[0][0][3]=0;
	for(int i=1;i<=n;i++){
		int ii=i&1,iii=ii^1; 
		for(int j=1;j<=n;j++){
			dp[ii][j][0]=dp[iii][j-1][3]; 
			dp[ii][j][1]=dp[iii][j-1][3]+a[i];
			dp[ii][j][2]=dp[iii][j-1][3]-a[i];
			dp[ii][j][3]=dp[iii][j-1][3];
			dp[ii][j][0]=max(dp[ii][j][0],dp[iii][j][0]);
			dp[ii][j][1]=max(dp[ii][j][1],max(dp[iii][j][0]+a[i],dp[iii][j][1]));
			dp[ii][j][2]=max(dp[ii][j][2],max(dp[iii][j][0]-a[i],dp[iii][j][2]));
			dp[ii][j][3]=max(dp[ii][j][3],max(dp[iii][j][1]-a[i],max(dp[iii][j][2]+a[i],dp[iii][j][3])));
		}
	}
	for(int i=1;i<=n;i++)cout<<dp[n&1][i][3]<<endl;
    return 0;
}

5.E - Crystal Switches
题意:有n个点m条边,每条边有有标号0/1,1代表可以通行,0不可以通行。同时还有k个特殊点,每次经过特殊点,全图的边的标号取反(能通行的变成不通行,不通行变成通行),问从1到n经过边的最小个数
题解:
考虑将点i拆成2个点,i2代表原图的i,i2+1代表原图边取反后的i
对于输入的边(x,y,z)
若z1,则x2跟y2 连一条权值为1 的边
若z
0,则x2+1跟y2+1 连一条权值为1 的边
对于特殊点s, s2跟 s2+1 连一条权值为0 的边
根据重新构造的图进行最短路即可

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=1e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

int n,m,k,s[maxn],sp[maxn];
int tot,head[maxn],nx[maxn],to[maxn],val[maxn];
void add(int x,int y,int v){to[++tot]=y;nx[tot]=head[x];head[x]=tot;val[tot]=v;}

struct node{
	ll dis,pos;
	bool operator <(const node &x)const{
		return x.dis<dis;
	} 
};
int dis[maxn],vis[maxn];
int Dij(int now){
	for(int i=1;i<=n*2+1;i++)vis[i]=0,dis[i]=inf;
	priority_queue<node>que;
	que.push({0,now});dis[now]=0;
	while(que.size()){
		auto x=que.top();que.pop();
		ll u=x.pos,diss=x.dis;
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i;i=nx[i]){
			int v=to[i],w=val[i];
			if(dis[v]>diss+w){
				dis[v]=diss+w;
				if(!vis[v])que.push({dis[v],v});
			}
		}
	}
	return min(dis[n*2],dis[n*2+1]);
} 

signed main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		if(z==1)add(x*2,y*2,1),add(y*2,x*2,1);
		else add(x*2+1,y*2+1,1),add(y*2+1,x*2+1,1);
	} 	
	for(int i=1;i<=k;i++){
		int x=read();
		add(x*2,x*2+1,0);add(x*2+1,x*2,0);
	}
	int ans=Dij(2);
	if(ans==inf)ans=-1;
	cout<<ans;
    return 0;
}

6.F - BOX
题意:最开始有 N 个篮子,最开始第 i 个篮子只包含球 i 。我们需要进行 Q 次操作,如果 type=1 ,我们需要将第 y 个篮子里面所有球都放到第 x 个篮子里面。如果 type=2 ,我们需要将第 k+1 个球放到第 x 个篮子里面,其中 k 是当前球的数量,如果 type=3 ,我们需要回答第 x 个球在哪个篮子里面。
题解:
关键在于操作1,如何快速合并?不难想到启发式合并
但是合并是小的合并到大的,而题目把y合并到x不一定满足条件
对于不满足条件的x,y的标号交换,使得把x合并到y等价于把y合并到x

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int id[maxn];	//id[i] 第i个球在id[i]box里 
int p[maxn];	//p[i]  标号为i是第p[i]个box 
int now[maxn];  //now[i] 表示第i个box在交互操作后的标号 
vector<vector<int> >G(maxn); 
void merge(int x,int y){
	for(auto i:G[x]){
		id[i]=y;
		G[y].pb(i);
	}
	G[x].clear();
	return ;
}
int main(){
	int n=read(),q=read();
	int sum=n;		//总球个数 
	for(int i=1;i<=n;i++){
		id[i]=now[i]=p[i]=i;
		G[i].pb(i);
	}
	while(q--){
		int opt=read();
		if(opt==1){
			int x=read(),y=read();
			if((int)G[now[x]].size()<(int)G[now[y]].size())swap(now[x],now[y]),p[now[x]]=x,p[now[y]]=y; 
			merge(now[y],now[x]);
		}
		else if(opt==2){
			int x=read();sum++;
			id[sum]=now[x];G[now[x]].pb(sum);
		}
		else {
			int x=read();
			cout<<p[id[x]]<<endl; 
		}
	}
    return 0;
}

7.G - At Most 2 Colors
题意:n个格子,每个格子可以填c种颜色,并且连续k个格子最多2中不同颜色,问方案数
题解:

点击查看代码
signed main(){
	int n=read(),k=read(),c=read();
	vector<int>dp(n+1);
	dp[1]=c%MOD;
	for(int i=2;i<=n;i++){
		int pre=max(i-k+1,1ll);
		dp[i]=(dp[i-1]-dp[pre])*2ll+dp[pre]*c;
		dp[i]%=MOD;
	} 
	cout<<(dp[n]%MOD+MOD)%MOD;
    return 0;
}

8.E - Don't Isolate Elements
题意:给你一个n*m的01字符串,你每次操作可以将某一行的0翻转成1,1翻转成0。问最少进行多少进行多少次操作,让这个字符串每个字符都不是“孤岛”。
“孤岛”的定义:这个字符的上下左右相邻字符均与它不同。也就是说,如果该字符为0 ,则它的上下左右字符均为1或者越过数组边界。
题解:
采用dp来做
最开始想了个dp[i][j][k]表示(i,j)为k的最小操作次数
但发现没法转移,第i行不仅跟第i-1行有关,而且与第i-2行业有关

因为每次操作都是对行来做的
所以我们设dp[i][j][k]为前i-1行必须合法(第i行不保证),第i行的操作状态为j(=0/1),第i-1行的操作状态为k(=0/1)时的最小操作次数

转移:
dp[i][j][k]=min{dp[i-1][k][?](?、k、j三状态要满足i-1行一定合法,这样才能转移)}

点击查看代码

int h,w,a[1003][1003],dp[1003][2][2]; 

signed main(){
	h=read();w=read();
	for(int i=1;i<=h;i++)for(int j=1;j<=w;j++)a[i][j]=read();
	memset(dp,0x3f,sizeof(dp));dp[0][0][0]=0;
	for(int i=1;i<=h+1;i++){
		for(int j=0;j<2;j++)for(int k=0;k<2;k++)for(int s=0;s<2;s++){
			/*
				i-->j
				i-1-->k
				i-2-->s
			*/
			bool fa=true;
			if(i==1){
				dp[i][j][k]=min(dp[i][j][k],dp[i-1][k][s]+j);
				continue;
			}
			for(int t=1;t<=w;t++){
				int up,back,lef,rig,now;
				up=(s?a[i-2][t]^1:a[i-2][t]);
				back=(j?a[i][t]^1:a[i][t]);
				lef=(k?a[i-1][t-1]^1:a[i-1][t-1]);
				rig=(k?a[i-1][t+1]^1:a[i-1][t+1]); 
				now=(k?a[i-1][t]^1:a[i-1][t]);
				now^=1;
				if(i==h+1)back=now; 	//第h+1行也是墙 
				if(i-2<=0)up=now;
				if(t-1<=0)lef=now;
				if(t+1>w)rig=now;
				now^=1;
				if(up==back && lef==rig && up==lef && up!=now){
					fa=false;
					break;
				}
			}
			if(fa)dp[i][j][k]=min(dp[i][j][k],dp[i-1][k][s]+j);
		}
	}
	dp[h+1][0][0]=min(dp[h+1][0][0],dp[h+1][0][1]);
	cout<<(dp[h+1][0][0]==dp[0][1][1]?-1:dp[h+1][0][0]); 
    return 0;
}

9.F - Permutation Distance
题意:
题解:

用单点修改,区间查询的线段树来维护第二维

点击查看代码

struct Seg_Tree{
	int tr[maxn];
	void build(int k,int l,int r){
		if(l==r){
			tr[k]=inf;
			return ;
		}
		int mid=(l+r)>>1;
		build(k<<1,l,mid);build(k<<1|1,mid+1,r);
		tr[k]=min(tr[k<<1],tr[k<<1|1]);
		return;
	}
	
	void update(int k,int l,int r,int pos,int val){
		if(r<pos || pos<l)return ;
		if(l==r){
			tr[k]=val;
			return ;
		}	
		int mid=(l+r)>>1;
		update(k<<1,l,mid,pos,val);update(k<<1|1,mid+1,r,pos,val);
		tr[k]=min(tr[k<<1],tr[k<<1|1]);
		return ; 
	}
	
	int query(int k,int l,int r,int L,int R){
		if(r<L || R<l)return inf;
		if(L<=l && r<=R)return tr[k];
		int mid=(l+r)>>1;
		return min(query(k<<1,l,mid,L,R),query(k<<1|1,mid+1,r,L,R));
	}
}T1,T2;
signed main(){
	int n=read();
	vector<int>p(n+1),ans(n+1);
	for(int i=1;i<=n;i++)p[i]=read(),ans[i]=inf;
	
	/*
	p_i+i+min(-p_j-j) (p_i>p_j,i>j)
	-p_i+i+min(p_j-j) (p_i<p_j,i>j)
	*/
	T1.build(1,1,n);
	T2.build(1,1,n);
	for(int i=1;i<=n;i++){
		ans[i]=min(ans[i],p[i]+i+T1.query(1,1,n,1,p[i]));
		ans[i]=min(ans[i],-p[i]+i+T2.query(1,1,n,p[i],n));
		T1.update(1,1,n,p[i],-p[i]-i);
		T2.update(1,1,n,p[i],p[i]-i);
	}
	
	/*
	p_i-i+min(-p_j+j) (p_i>p_j,i<j)
	-p_i-i+min(p_j+j) (p_i<p_j,i<j) 
	*/
	T1.build(1,1,n);
	T2.build(1,1,n);
	for(int i=n;i>=1;i--){
		ans[i]=min(ans[i],p[i]-i+T1.query(1,1,n,1,p[i]));
		ans[i]=min(ans[i],-p[i]-i+T2.query(1,1,n,p[i],n));
		T1.update(1,1,n,p[i],-p[i]+i);
		T2.update(1,1,n,p[i],p[i]+i);
	}
	
	for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
    return 0;
}

10.G - Partial Xor Enumeration
题意:给你一个长度为n的数组,将它所有子序列(可能为空)进行XOR 操作后得到的值放进一个有序的数组 S。求数组的LR项分别为多少。
题解:
线性基模板题
注意空集为0也在S数组里,而线性基任意一个子集的异或和都不为0,特殊考虑(L-1,R-1)即可

点击查看代码
int n,l,r;
struct Lbase{
    ll p[101],d[101];
    int cnt,lens;
    void init(){
        memset(p,0,sizeof(p));
        memset(d,0,sizeof(d));
        cnt=0;lens=0;
    }
    void insert(ll x){//插入
        if(x==0)return ;
        for(int i=lens;i>=0;i--){
            if((1ll<<i)&x){
                if(p[i])x=x^p[i];
                else {p[i]=x;break;}
            }
        }
        return;
    }
    
    void rebuild(){ //重构
        cnt=0;
        for(int i=lens;i>=0;i--){
            for(int j=i-1;j>=0;j--){
                if(p[i]&(1ll<<j))p[i]^=p[j];
            }
        }
        for(int i=0;i<=lens;i++)if(p[i])d[cnt++]=p[i];
    } 
    int Kth(int pos){  //找第k小
        ll ans=0;
        for(int i=lens;i>=0;i--){
            if(pos&(1ll<<i))ans^=d[i];
        }
        return ans;
    }
    
}T;
signed main(){
	n=read(),l=read(),r=read();
	vector<int>a(n+1);
	T.lens=60;
	for(int i=1;i<=n;i++)T.insert(read());
	T.rebuild();
	for(int i=l-1;i<=r-1;i++)cout<<T.Kth(i)<<" ";
    return 0;
}
posted @   I_N_V  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2021-10-16 排列和组合
点击右上角即可分享
微信分享提示