2025“钉耙编程”中国大学生算法设计春季联赛(4)
优秀是一种习惯
持家
#include <bits/stdc++.h>
using namespace std;
#define int long long
vector<int>a[2];
int s[10005];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int p,n,k;
cin>>p>>n>>k;
for(int i=1;i<=n;i++)
{
int t,p;
cin>>t>>p;
a[t].push_back(p);
}
sort(a[0].begin(),a[0].end());
sort(a[1].begin(),a[1].end());
reverse(a[1].begin(),a[1].end());
for(int i=0;i<a[1].size();i++)
{
s[i+1]=s[i]+a[1][i];
}
double ans=p,cur=1;
for(int i=0;i<=min((int)a[0].size(),k);i++)
{
ans=min(ans,p*cur-s[min((int)a[1].size(),k-i)]);
cur=cur/10.0*a[0][i];
}
cout<<fixed<<setprecision(2)<<max(ans,0.0)<<"\n";
a[0].clear();
a[1].clear();
}
return 0;
}
制衡
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int n,k;
cin>>n>>k;
vector<vector<int> >a(n+1,vector<int>(k+1));
vector<vector<int> >f(n+1,vector<int>(k+1));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
f[i][j]=max({f[i][j-1],f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]});
}
}
cout<<f[n][k]<<"\n";
}
return 0;
}
进步
- 除法本身就是向0取整,右移才是向下取整,所以二分的时候才要用右移
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[200005],c[200005],n;
int lowbit(int n)
{
return n&(-n);
}
void add(int n1,int va)
{
while(n1<=n)
{
c[n1]+=va;
n1+=lowbit(n1);
}
}
int ask(int n1)
{
int ans=0;
while(n1>0)
{
ans+=c[n1];
n1-=lowbit(n1);
}
return ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int q;
cin>>n>>q;
memset(c,0,sizeof(c[0])*(n+1));
for(int i=1;i<=n;i++)
{
cin>>a[i];
add(i,a[i]);
}
int tot=0,ans=0;
for(int i=1;i<=q;i++)
{
int opt,x,y;
cin>>opt>>x>>y;
if(opt==1)
{
add(x,y-a[x]);
a[x]=y;
}
else
{
tot++;
int u=ask(x-1)/100;
int v=ask(y)/100;
ans^=((v-u)*tot);
}
}
cout<<ans<<"\n";
}
return 0;
}
战斗爽
- 一开始没意识到最后一次攻击可能会导致下一次受到的伤害变化,所幸样例把这种错误测出来了,下次还是要更谨慎一些;即使真的提交错了也不要太过质疑自己,你看有国集选手都错了四次才通过这道题呢
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[10005],h[10005],u,p[10005],q[10005];
bool l[10005];
int calc(int i)
{
int hq=h[i];
hq-=u;
if(hq<=0)
{
return 1;
}
return 1+hq/(u/2)+(hq%(u/2)>0);
}
bool cmp1(int x,int y)
{
return a[x]>a[y];
}
bool cmp2(int x,int y)
{
if(h[x]!=h[y])
{
return h[x]<h[y];
}
if(a[x]!=a[y])
{
return a[x]<a[y];
}
return x<y;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int n,k,hq;
cin>>n>>u>>k>>hq;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>h[i];
p[i]=q[i]=i;
l[i]=true;
}
sort(p+1,p+n+1,cmp1);
sort(q+1,q+n+1,cmp2);
int cur=1,i=1;
do
{
int cnt=calc(q[i]);
if(cnt<=k)
{
if(hq<=(cnt-1)*a[p[cur]])
{
break;
}
hq-=(cnt-1)*a[p[cur]];
l[q[i]]=false;
while(cur<=n&&l[p[cur]]==false)
{
cur++;
}
if(cur>n)
{
break;
}
hq-=a[p[cur]];
}
else
{
hq-=k*a[p[cur]];
}
i++;
}while(hq);
cout<<n-accumulate(l+1,l+n+1,0)<<"\n";
}
return 0;
}
洞察
#include <bits/stdc++.h>
using namespace std;
#define int long long
int k,b;
int calc(int l,int r)
{
if(l%k!=b%k)
{
l=l+(b%k-l%k+k)%k;
}
if(r%k!=b%k)
{
r=r-(r%k-b%k+k)%k;
}
if(l>r)
{
return 0;
}
return (r-l)/k+1;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int c,v;
cin>>k>>b>>c>>v;
int cur=0,ans=0;
for(int i=59;i>=0;i--)
{
if((v>>i)&1)
{
ans+=calc(max(cur+(1ll<<i)*((c>>i)&1),b),cur+(1ll<<i)*((c>>i)&1)+(1ll<<i)-1);
}
cur+=(1ll<<i)*(((c>>i)&1)^((v>>i)&1));
}
if(cur>=b&&cur%k==b%k)
{
ans++;
}
cout<<ans<<"\n";
}
return 0;
}
充实
- 首先发现如果序列中存在两个相连的数,那么这条支线就一定是充实的
- 接下来尝试一下两个数的情况,发现序列完备当且仅当两数差为二的次幂。
- 于是大胆猜想一条支线充实当且仅当存在两个数的差为二的次幂
- 提交,Wrong Answer 。结论不对吗?继续尝试,发现了反例,那看来结论确实有误,但我觉得“差为二的次幂”的结论是有意义的,沿着这条道路思考,用心感受数与数的变换——
- 首先,数的绝对取值是没有意义的,所以可以让树的所有节点都减去根节点的权值,注意根节点要最后操作
- 然后,或许,一条支线充实,当且仅当,它们的 GCD ,为二的次幂?
- 修正代码,提交,仍是 Wrong Answer
- ——还是,不对吗……
- 回望来时的路,一步步走来——我觉得这个结论是有道理的
- ——那,会是其他地方出了问题吗?比如…特判?
- 对,特判,GCD 为 0 ,即所有数相同的情况同样是合法的,但你之前没考虑到。
- 改好代码、测试、提交
- Accepted
- ad-hoc:就是说这个问题的解法没法归结成更通用的东西
#include <bits/stdc++.h>
using namespace std;
vector<int>a[100005];
stack<int>s;
int c[100005];
int ans;
void dfs(int u)
{
if(u==1)
{
s.push(0);
}
else
{
s.push(__gcd(s.top(),c[u]));
}
if(!a[u].size())
{
for(int i=0;(1<<i)<=s.top();i++)
{
if(s.top()==(1<<i))
{
ans++;
break;
}
}
ans+=(!s.top());
}
for(int v:a[u])
{
dfs(v);
}
s.pop();
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
a[i].clear();
}
for(int i=2;i<=n;i++)
{
int fa;
cin>>fa;
a[fa].push_back(i);
}
for(int i=1;i<=n;i++)
{
cin>>c[i];
}
for(int i=n;i>=1;i--)
{
c[i]-=c[1];
}
ans=0;
dfs(1);
cout<<ans<<"\n";
}
return 0;
}
图之图
- 读错题了,但我觉得不能简单归因于读题不仔细,而是你对无序这个关键词缺乏足够的敏感度
- 也模拟过样例了,你得出的答案是3,可样例是5呀,你怎么会觉得 3 和 5 是一样的…
- 告诉自己要认真读题,自己也确实这么做了,但下次或许可以做得更好一些,让自己的心安宁下来
- 这种情况肯定要尽可能避免,但你也不能保证正式比赛 100% 不会发生这种事情呀,所以今天就权当演练了;你看,天没有塌,在这之后,你对题目的理解也更加透彻了
- 在 i 小于 j 的连边限制下,有向图的拓扑序必定是 1,2,3,…,n
- 确实一直以来都没怎么接触过根号分治的题目,而你能在赛时想到这个做法,已经很棒了👍
- 注意自环不能添加两次!多点教训总是好的
#include <bits/stdc++.h>
using namespace std;
vector<int>a[100005],x;
int b[100005];
const int mod=1000000007;
int cnt[100005],n,k,z,val[100005],ex[100005];
void topsort()
{
for(int u=1;u<=n;u++)
{
if(a[b[u]].size()<z)
{
for(int v:a[b[u]])
{
(cnt[u]+=val[v])%=mod;
}
}
else
{
cnt[u]+=ex[b[u]];
}
(val[b[u]]+=cnt[u])%=mod;
int y=0;
for(int i=0;i<x.size();i++)
{
while(y<a[b[u]].size()&&a[b[u]][y]<x[i])
{
y++;
}
if(y!=a[b[u]].size()&&a[b[u]][y]==x[i])
{
(ex[x[i]]+=cnt[u])%=mod;
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
while(T--)
{
x.clear();
cin>>n>>k;
for(int i=1;i<=k;i++)
{
a[i].clear();
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
int m;
cin>>m;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
a[u].push_back(v);
if(u!=v)
{
a[v].push_back(u);
}
}
z=sqrt(2*m);
for(int i=1;i<=k;i++)
{
sort(a[i].begin(),a[i].end());
if(a[i].size()>=z)
{
x.push_back(i);
}
}
memset(cnt,0,sizeof(cnt[0])*(n+1));
memset(val,0,sizeof(val[0])*(k+1));
memset(ex,0,sizeof(ex[0])*(k+1));
cnt[1]=1;
topsort();
cout<<cnt[n]<<"\n";
}
return 0;
}
童年
- 看上去是非公平组合游戏,但如果淡化A和B的角色,用先手和后手的视角看待整个局面,每次操作交换前后两项,就等价的得到了一个公平组合游戏
- 经典的博弈论建模,但一般情况下这种方法都只用来分析问题,需要程序求解的题目确实见得不多
- 采取类似数学期望DP的倒推方法,将所有能到达必败态的点标记为必胜态,统计一个点能到达的必胜态的点的数量是否等于其出度
- 强制要求后面的数不小于前面的数,简化问题
#include <bits/stdc++.h>
#define int long long
using namespace std;
int enc(int i,int j,int k,int l)
{
if(i>j)
{
swap(i,j);
}
if(k>l)
{
swap(k,l);
}
return ((i*10+j)*10+k)*10+l;
}
vector<int>a[10000],b[10000];
int ans[10000],cnt[10000];
queue<int>q;
void add(int u,int v)
{
a[u].push_back(v);
b[v].push_back(u);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
for(int i=0;i<10;i++)
{
for(int j=max(i,1ll);j<10;j++)
{
ans[enc(i,j,0,0)]=2;
q.push(enc(i,j,0,0));
}
}
for(int i=0;i<10;i++)
{
for(int j=max(i,1ll);j<10;j++)
{
for(int k=0;k<10;k++)
{
for(int l=max(k,1ll);l<10;l++)
{
int u=enc(i,j,k,l);
add(u,enc(k,l,i,(j+l)%10));
if(i!=0)
{
add(u,enc(k,l,(i+l)%10,j));
}
if(k!=0)
{
add(u,enc(k,l,i,(j+k)%10));
}
if(i!=0&&k!=0)
{
add(u,enc(k,l,(i+k)%10,j));
}
}
}
}
}
while(q.size())
{
int u=q.front();
q.pop();
for(int v:b[u])
{
if(ans[u]==2)
{
if(ans[v]==0)
{
ans[v]=1;
q.push(v);
}
}
else
{
cnt[v]++;
if(cnt[v]==a[v].size())
{
ans[v]=2;
q.push(v);
}
}
}
}
int T;
cin>>T;
while(T--)
{
int i,j,k,l;
cin>>i>>j>>k>>l;
cout<<ans[enc(i,j,k,l)]<<"\n";
}
return 0;
}

浙公网安备 33010602011771号