good-problems 11
1.D. Paths on the Tree
题意:
题解:
首先根据题目的限制,我们从上往下考虑
第一个点一定是k次经过,那么它的儿子们要不是次经过,要不就是次
由此可以知道每个点的经过次数,一定是不变的两个可能
为什么不变,是固定的两个可能?难道祖先一直多取一个,就是使当前节点的可能更大?
考虑性质,同父亲的两个点的经过次数只差最多只有1,那么+1往下传递的影响可看作没有,所以每个点取的可能只有固定的2种
知道了这个性质就好做了
设 表示u节点经过k或(k+1)次的最大值
考虑如何转移,
我们先求出所有u的儿子v的dp值
如果u平均分k给u的儿子后还多出一些经过次数,就把这些多余分配次数给大的儿子
先取delt大的儿子的,再去其他的
的转移略有不同,因为的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;
}
点击查看代码
#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 的边
若z0,则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
题意:给你一个长度为的数组,将它所有子序列(可能为空)进行 操作后得到的值放进一个有序的数组 。求数组的到项分别为多少。
题解:
线性基模板题
注意空集为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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-10-16 排列和组合