CCPC online 2025题解 ( A~H+K+M)
没代码的就是队友写的,只提供思路
E
签到题,首先可以算出两人胜利的场次数,答案就是较小的那个*2+1
K
找规律题,打个表可以发现输出 \(n\) 到 \(1\) 即可
A
签到题,枚举每种正方形的边对应的向量,则能构成该种正方形的顶点个数应当构成一个矩形,直接二维差分即可
G
赛事写的根号分治,然而可以根本不用
记下所有数出现的位置,已经出现次数 \(c_i\)
对于一次询问,直接枚举两个数中出现次数较小的数的所有位置,二分另一处即可
复杂度证明应该类似启发式(笔者没证),注意记忆化或特判相等情况就好了
B
这题跟 AGC023F 思路做法一模一样,感觉出题人就是根据这个出的
只要做过这个题的基本都能过(然而我们队没判相等条件,喜提 WA )
首先我们的攻击力是不变的,可以先算出每个怪兽需要打的次数 \(d_i\),设进入 \(u\) 点时我们的防御力为 \(T\) ,则我们经过这个点的扣血量为 \(d_i*(atk_i-Y)= d_i*atk_i-d_i*Y\),前者固定,最大化 \(d_i*Y\) 即可
问题转化为 ,经过点可以给 \(Y\) 加一或给答案增加 \(d_i*Y\) ,问答案最大值
类似那题,我们考虑贪心,每次将最优点向父亲合并,表示进行完父亲以及之前已经向父亲合并的所有节点后再走这个点
如何找最优解,考虑临项交换,设该已经相互合并的点的 \(t_i=1\) 的节点个数为 \(c_u\) , \(d_i\) 之和为 \(r_u\),考虑两个不同的集合 \(u,v\),两种情况分别为
1.\(Y\) 进 \(u\) 贡献的答案 \(+\) \(Y\) 进 \(v\) 贡献的答案 \(+\) \(c_u*r_v\) (先进 \(u\) 后进 \(v\))
2.\(Y\) 进 \(v\) 贡献的答案 \(+\) \(Y\) 进 \(u\) 贡献的答案 \(+\) \(c_v*r_u\) (先进 \(u\) 后进 \(v\))
因此只需比较不同的代价即可知按 \(\frac{c_u}{r_u}\) 排序即可
注意相等情况
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,X;
struct edge{
int to,nex;
}e[640010];
int head[200100],cnt;
void add(int u,int v)
{
e[++cnt]={v,head[u]};
head[u]=cnt;
}
int fa[200100];
int dsu[200100];
void dfs(int now,int fath)
{
fa[now]=fath;
for(int i=head[now];i;i=e[i].nex)
{
int v=e[i].to;
if(v==fath) continue;
dfs(v,now);
}
}
long long ans;
struct node{
int x,y,id;
bool friend operator<(node a,node b)
{
if(a.x*b.y==a.y*b.x)
{
if(a.x==b.x)
{
return a.id<b.id;
}
return a.x>b.x;
}
return a.x*b.y>a.y*b.x;
}
}a[640010];
set<node>q;
int find(int x){return x==dsu[x]?x:dsu[x]=find(dsu[x]);}
signed main()
{
// freopen("txt.in","r",stdin);
// freopen("txt.out","w",stdout);
cin>>n>>X;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
dfs(1,0);
for(int i=2;i<=n;i++)
{
int t;
cin>>t;
if(t==1)
{
a[i].x=1;a[i].y=0;
}
else
{
int atk,def,hp;
cin>>atk>>def>>hp;
a[i].x=0;
a[i].y=(hp%(X-def)==0?hp/(X-def):hp/(X-def)+1)-1;
ans-=a[i].y*atk;
}
a[i].id=i;
q.insert(a[i]);
}
for(int i=1;i<=n;i++) dsu[i]=i;
while(!q.empty())
{
node u=*q.begin();q.erase(q.begin());
int fai=find(fa[u.id]);
if(fai!=1) q.erase(q.find(a[fai]));
ans+=a[fai].x*a[u.id].y;
a[fai].x+=a[u.id].x;
a[fai].y+=a[u.id].y;
dsu[u.id]=fai;
if(fai!=1) q.insert(a[fai]);
}
cout<<ans<<endl;
return 0;
}
C
据说有巨多种贪心方法,这里讲一下我们队赛事的方法
见完全图最小生成树考虑 Boruvka 算法
只需靠考虑找出 $ (t_v+t_u)\mod k$ 的最小值即可,枚举 \(t_u\) 显然能成为最小值的 \(v\) 只能是 \(\leq 0\) 或 \(\leq k-t_u\) 的最小值取其一,直接用 set 即可
然而我们队非得写线段树二分(感觉赛事我们好傻)
贴个赛事代码(写的又臭又长,没啥参考性)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,M=6e6+10,inf=2e9;
int t[N];
int sum[M],ls[M],rs[M];
int idm[M],idmt[M];
int vis[N],ccnt;
set<int>id[N];
int clr[N],clrtot;
struct dsu{
int fa[N];
inline void init(int n){for(int i=1;i<=n;i++) fa[i]=i;}
inline int find(int x){return fa[x]==x?x:fa[x] =find(fa[x]);}
}dsu;
inline void upd(int &p,int l,int r,int pos,int v,int x){
if(!p) p = ++ccnt, idm[p]=inf,idmt[p]=-1,ls[p]=rs[p]=sum[p]=0;
if(l==r)
{
sum[p] += v;
pos=lower_bound(clr+1,clr+1+clrtot,pos)-clr;
if(v==1) id[pos].insert(x);
else id[pos].erase(x);
if(sum[p]>=1) idm[p]=l,idmt[p]=*id[pos].begin();
else idm[p]=inf,idmt[p]=-1;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) upd(ls[p],l,mid,pos,v,x);
else upd(rs[p],mid+1,r,pos,v,x);
sum[p] = sum[ls[p]] + sum[rs[p]];
if(ls[p]&&idm[ls[p]]!=inf) idm[p]=idm[ls[p]],idmt[p]=idmt[ls[p]];
else if(rs[p]) idm[p]=idm[rs[p]],idmt[p]=idmt[rs[p]];
else idm[p]=inf;
if(idm[p]==inf) idmt[p]=-1;
return ;
}
inline int findfirst(int p,int l,int r,int bound){
if(!p) return -1;
if(bound<=l) return idmt[p];
if(l==r)
{
if(sum[p]) return *id[lower_bound(clr+1,clr+1+clrtot,l)-clr].begin();
else return -1;
}
int mid=(l+r)>>1;
if(bound<=mid)
{
int rl=findfirst(ls[p],l,mid,bound);
if(rl!=-1) return rl;
}
return findfirst(rs[p],mid+1,r,bound);
}
vector<int>vc[N];
int rt;
struct node{
int u,v,w;
}e[N];
int cnt;
bool cmp(node x,node y){return x.w<y.w;}
int main(){
int T;
cin>>T;
while(T--)
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>t[i];
t[i]%=k;
vc[i].push_back(i);
clr[++clrtot]=t[i];
}
sort(clr+1,clr+1+n);
clrtot=unique(clr+1,clr+1+n)-clr-1;
for(int i=1;i<=n;i++)
{
upd(rt,0,k,t[i],1,i);
}
int tot=n;
long long ans=0;
dsu.init(n);
while(1)
{
for(int i=1;i<=n;i++) vis[i]=0;
if(tot==1) break;
cnt=0;
dsu.init(n);
for(int i=1;i<=tot;i++)
{
for(int j=0;j<vc[i].size();j++)
{
dsu.fa[vc[i][j]]=vc[i][0];
upd(rt,0,k,t[vc[i][j]],-1,vc[i][j]);
}
for(int j=0;j<vc[i].size();j++)
{
int now=t[vc[i][j]];
int l=findfirst(rt,0,k,0),r=findfirst(rt,0,k,k-now);
if(r==-1) {cnt++;e[cnt]={vc[i][j],l,(now+t[l])%k};}
else if(l==-1) {cnt++;e[cnt]={vc[i][j],r,(now+t[r])%k};}
else if((now+t[l])%k<=(now+t[r])%k)
{
cnt++;
e[cnt]={vc[i][j],l,(now+t[l])%k};
}
else
{
cnt++;
e[cnt]={vc[i][j],r,(now+t[r])%k};
}
}
for(int j=0;j<vc[i].size();j++)
{
upd(rt,0,k,t[vc[i][j]],1,vc[i][j]);
}
}
sort(e+1,e+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
int u=dsu.find(e[i].u),v=dsu.find(e[i].v);
if(u!=v)
{
ans+=e[i].w;
dsu.fa[v]=u;
}
}
for(int i=1;i<=tot;i++)
{
vc[i].clear();
}
tot=0;
for(int i=1;i<=n;i++)
{
if(dsu.fa[i]==i)
{
tot++;vis[i]=tot;
vc[tot].push_back(i);
}
}
for(int i=1;i<=n;i++)
{
if(dsu.fa[i]!=i)
{
vc[vis[dsu.find(i)]].push_back(i);
}
}
}
cout<<ans<<endl;
for(int i=1;i<=clrtot;i++) id[i].clear();
clrtot=0;
ccnt=rt=0;
for(int i=1;i<=n;i++) vc[i].clear();
}
return 0;
}
D
典题,注意到通配符至多只有 \(10\) 个,考虑先删去通配符,将剩余的每个字符串都跑一边 kmp
枚举子串的左端点 \(l\) ,由于通配符的存在,我们可以尽可能贪心的匹配最后一个通配符之前的所有字符串,再找到最后一个字符串所有符合条件的位置。
注意不要写二分,用双指针即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define int long long
const int N=5e5+10;
ull hs[N],pw[N];
inline ull geths(int l,int r){return hs[r] - hs[l-1] * pw[r-l+1];}
char s[N],t[N];
const ull base = 13331;
signed main(){
// ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
scanf("%s%s",s+1,t+1);
int n = strlen(s+1),m= strlen(t+1);
pw[0] = 1;
for(int i=1;i<=m;++i){
hs[i] = hs[i-1] * base + t[i];
pw[i] = pw[i-1] * base;
}
string lst = "";
bool lef = s[1] == '*';
bool rig = s[n] == '*';
vector<string> sub;
for(int i=1;i<=n;++i){
if(s[i] == '*'){
if(lst != "") sub.emplace_back(lst);
lst = "";
}else{
lst += s[i];
}
}
if(!rig) sub.emplace_back(lst),lst = "";
// if(sub.size() == 0) cout<<m*(m+1)/2<<endl,exit(0);
vector<vector<int> > lpos(sub.size());
vector<int> iter(sub.size());
int cnt = 0,ans = 0;
for(int it = 0;it<sub.size();++it){
string str = sub[it];
ull hs_s = 0;
for(int j=0;j<str.length();++j) hs_s = hs_s * base + str[j];
for(int j=1;j<=m;++j){
if(j+str.length()-1>m) break;
if(geths(j,j+str.length()-1) == hs_s) lpos[cnt].emplace_back(j);
}
// for(auto x:lpos[cnt]) cout<<x<<" "; cout<<endl;
cnt += 1;
}
for(int l=1;l<=m;++l){
int tar = l;
bool flag = true;
for(int j=0;j<cnt;++j){
while(iter[j] < lpos[j].size() && lpos[j][iter[j]]<tar) iter[j] ++;
if(iter[j]>=lpos[j].size()){
flag = false;
break;
}
tar = lpos[j][iter[j]] + sub[j].size();
}
if(!flag) break;
// tar is the minimum R
if(lef == false && lpos[0][iter[0]] != l) continue;
if(rig){
if(cnt != 0) ans += m- tar + 2;
else ans += m-l+1;
}else{
ans += lpos[cnt -1].size() - iter[cnt-1];
}
}
printf("%lld\n",ans);
return 0;
}
F
典中典博弈论,赛事就是不打表(恼)
首先考虑博弈论部分 ,参考这个题的打表 (CF1704F),你会发现这两个题的 \(SG\)计算过程一模一样,直接套过来就行了
对于初始局面的寻找,可以用 XOR hash 快速定位出所有的子游戏,然后就做完了
H
观察一个性质,每一门课最终只会被一个老师影响
因此我们可以钦定每门课被那个老师影响,即使不合法(比如选了另一个老师的值大于当前钦定的)也不影响答案,因为合法的显然更优
因此考虑 \(dp\) ,记 \(dp_{i,j,S}\) 表示考虑到前 \(i\) 个老师,用了 \(j\) 时间,已经钦定的课程集合为 \(S\) ,直接转移即可
注意转移时不要枚举子集,可以枚举每一门课,从该位为 0 的向 该位为 1 的所有状态转移,就可以做到 \(O(mTn2^n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,k,T;
int len[55],t[55];
int l[55][20],to[55][20],clr[55][20],Si[55];
int v[20][10001];
int dp[2][51][16385];
int tmp[16385];
signed main()
{
// freopen("txt.in","r",stdin);
// freopen("txt.out","w",stdout);
cin>>n>>m>>k>>T;
int sum=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
cin>>v[i][j];
}
sum+=v[i][0];
}
for(int i=1;i<=m;i++)
{
cin>>len[i]>>t[i];
for(int j=1;j<=len[i];j++)
{
cin>>l[i][j]>>to[i][j];
Si[i]|=(1<<(l[i][j]-1));
clr[i][l[i][j]]=v[l[i][j]][to[i][j]]-v[l[i][j]][0];
}
}
memset(dp,-1,sizeof(dp));
dp[0][0][0]=sum;
int now_=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<=T;j++)
{
for(int S=0;S<(1<<n);S++)
{
dp[now_^1][j][S]=max(dp[now_^1][j][S],dp[now_][j][S]);
}
}
for(int j=0;j<=T-t[i+1];j++)
{
for(int S=0;S<(1<<n);S++)
{
tmp[S]=-1;
}
for(int rp=1;rp<=n;rp++)
{
if(!(Si[i+1]&(1<<(rp-1)))) continue;
for(int S=0;S<(1<<n);S++)
{
if(!(S&(1<<(rp-1))))
{
if(dp[now_][j][S]!=-1) tmp[S|(1<<(rp-1))]=max(tmp[S|(1<<(rp-1))],dp[now_][j][S]+clr[i+1][rp]);
if(tmp[S]!=-1) tmp[S|(1<<rp-1)]=max(tmp[S|(1<<(rp-1))],tmp[S]+clr[i+1][rp]);
}
}
}
for(int S=0;S<(1<<n);S++)
{
dp[now_^1][j+t[i+1]][S]=max(dp[now_^1][j+t[i+1]][S],tmp[S]);
}
}
now_^=1;
}
for(int i=1;i<=T;i++)
{
sum=0;
for(int j=0;j<=i;j++)
{
for(int S=0;S<=(1<<n)-1;S++) sum=max(sum,dp[now_][j][S]);
}
cout<<sum<<endl;
}
return 0;
}
M
首先,我们必须整出一种确定的步骤来达到要求
我们可以先求出前缀和,之后,对于每一个位置的前缀和,为了保证分段,我们需要找到其之前第一个 \(1\) ,把那里的前缀和减掉。
这个操作比较困难,但注意其他位置是 \(0\) ,且前缀和具有单调性,考虑乘 \(0/1\) 后的前缀 \(\max\) 发现可行。
换句话说,答案即为
\(presum-premax\{presum*Y\}\)
乘和减是好做的,考虑前缀如何做
把数组分成 32 个块,每个块内部分别做前缀操作,然后再从第一个块递推到最后一个块(把上一个块最后一个数与下一个块的所有数做一次操作,以此类推),就可以了
计算一次前缀是 62 次,减和乘都是 32 次 一共是 188 次
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
struct node{
int a,b,c;
};
node clr[201][35];
string op[201];
int len[201];
int rp[35];
void output()
{
cout<<n<<endl;
for(int i=1;i<=n;i++)
{
cout<<op[i]<<" "<<len[i]<<endl;
for(int j=1;j<=len[i];j++)
{
cout<<clr[i][j].a<<" ";
}
cout<<endl;
for(int j=1;j<=len[i];j++)
{
cout<<clr[i][j].b<<" ";
}
cout<<endl;
for(int j=1;j<=len[i];j++)
{
cout<<clr[i][j].c<<" ";
}
cout<<endl;
}
}
int main()
{
// freopen("txt.in","r",stdin);
freopen("txt.in","w",stdout);
for(int i=1,now=0;i<=32;i++)
{
rp[i]=now;now+=32;
}
for(int T=1;T<=31;T++)
{
n++;
op[n]="add";
len[n]=32;
for(int i=1;i<=32;i++)
{
clr[n][i]={rp[i],rp[i]+1,rp[i]+1};
rp[i]++;
}
}
int now=31;
while(now<1023)
{
n++;
op[n]="add";
len[n]=32;
for(int i=now+1;i<=now+32;i++)
{
clr[n][i-now]={now,i,i};
}
now+=32;
}
cout<<n<<endl;
for(int i=1,now=0;i<=32;i++)
{
rp[i]=now;now+=32;
}
for(int T=1;T<=32;T++)
{
n++;
op[n]="mul";len[n]=32;
for(int i=1;i<=32;i++)
{
clr[n][i]={rp[i],rp[i]+1024,rp[i]+1024};
rp[i]++;
}
}
for(int i=1,now=1024;i<=32;i++)
{
rp[i]=now;now+=32;
}
for(int T=1;T<=31;T++)
{
n++;
op[n]="max";len[n]=32;
for(int i=1;i<=32;i++)
{
clr[n][i]={rp[i],rp[i]+1,rp[i]+1};
rp[i]++;
}
}
now=1024+31;
while(now<2047)
{
n++;
op[n]="max";
len[n]=32;
for(int i=now+1;i<=now+32;i++)
{
clr[n][i-now]={now,i,i};
}
now+=32;
}
for(int i=1,now=0;i<=32;i++)
{
rp[i]=now;now+=32;
}
for(int T=1;T<=32;T++)
{
n++;
op[n]="sub";len[n]=32;
for(int i=1;i<=32;i++)
{
clr[n][i]={rp[i],rp[i]+1024,rp[i]};
rp[i]++;
}
}
output();
return 0;
}

浙公网安备 33010602011771号