2024.12.15 THUPC2025初赛
Solved: 6/13
Rank: 295
300 多人过的网络流题没思路,直接从 200- 掉到 300-……
M. 好成绩
致敬主=6()
print(83)
C. Harmful Machine Learning
题意:\(n\) 个格子排成一列,初始棋子位于第 \(x\) 个格子。两人先后操作,每一轮先手可交换两个格子的数(可以不换),后手可将棋子移动到相邻格子(可以不动)或结束。
先手希望结束的时候棋子所在的格子中的数最小,后手希望最大,求最优决策下的答案。
一步之后,先手就可以将最小的三个数都移到后手的格子以及相邻两个格子的位置。因此后手的唯一机会就在第一步。
先手针对后手的第一步可以将最小值移到到后手下一步能到的三个格子中,因此后手第一步只能取到这三个格子中第二大的值。
答案就是 \(\max\{a_{(3)},\{a_{x-1},a_x,a_{x+1}\}_{(2)}\}\)。
特判 \(n=1,n=2\) 和 \(x=1,x=n\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
void solve(){
int n,x;
cin>>n>>x,--x;
vector<int> a(n);
for(int i=0;i<n;++i)cin>>a[i];
vector<int> b(a);
sort(all(b));
if(n<=3){
cout<<b[n-1]<<'\n';
}
else{
if(x==0){
cout<<max(min(a[0],a[1]),b[2])<<'\n';
}
else if(x==n-1){
cout<<max(min(a[n-1],a[n-2]),b[2])<<'\n';
}
else{
vector<int> c({a[x-1],a[x],a[x+1]});
sort(all(c));
cout<<max(c[1],b[2])<<'\n';
}
}
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
I. 乒乓球赛
题意:两人打乒乓球,10平前先得11分者胜,10平后先超对方2分者胜。已知比赛过程中的一些比分情况(顺序可能颠倒,即 3:5 念成 5:3),求符合已知条件的方案数。\(n\leq 10^5\)。
dp,\(f_i,g_i\) 表示第 \(i\) 轮比分没颠倒/颠倒的方案数。转移形如 \(f_j\times F(a_j,b_j,a_i,b_i)\rightarrow f_i\)。其中 \(F(a,b,c,d)\) 是从 \((a,b)\) 到 \((c,d)\) 的方案数。
如果两个状态都没到 10 平,直接组合数即可(\(11\) 视为 \(10\),因为赢的一球必然是最后一球);
如果两个状态都过了 10 平,则 \(a-b,c-d\) 取值都只能是 \(-1,0,1\)(\(2\) 视为 \(1\),\(-2\) 视为 \(-1\))。直接预处理出这部分 dp 即可。
如果前面没过后面过了,则一定存在某一时刻比分是 10 平,两段分开乘即可。
注意特判各种无解不合法情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
const int N=1e5+5,mod=998244353;
int c[21][21];
ll h[N][3][3];
void init(){
for(int i=0;i<=20;++i){
c[i][0]=1;
for(int j=1;j<=i;++j)
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
h[0][0][0]=h[0][1][1]=h[0][2][2]=1;
int m=1e5;
for(int i=0;i<=m;++i)
for(int j=0;j<3;++j){
(h[i+1][j][0]+=h[i][j][1])%=mod;
(h[i+1][j][1]+=h[i][j][0])%=mod;
(h[i+1][j][1]+=h[i][j][2])%=mod;
(h[i+1][j][2]+=h[i][j][1])%=mod;
}
}
ll F(int x1,int y1,int x2,int y2){
if(x2<x1||y2<y1)return 0;
if(x2+y2<=20){
x2=min(x2,10),y2=min(y2,10);
return c[x2+y2-x1-y1][x2-x1];
}
if(x1+y1>=20){
int s=x2+y2-x1-y1,d1=x1-y1,d2=x2-y2;
if(d2==2)--s,--d2;
if(d2==-2)--s,++d2;
return h[s][d1+1][d2+1];
}
return F(x1,y1,10,10)*F(10,10,x2,y2)%mod;
}
ll solve(){
int n;
cin>>n;
vector<int> a(n+1),b(n+1);
vector<ll> f(n+1),g(n+1);
for(int i=1;i<=n;++i){
cin>>a[i]>>b[i];
if(a[i]<b[i])swap(a[i],b[i]);
}
if(n<=20){
if(a[n]!=-1&&(a[n]!=11||b[n]!=n-11))return 0;
a[n]=11,b[n]=n-11;
}
else{
if(n%2==1)return 0;
if(a[n]!=-1&&(a[n]!=n/2+1||b[n]!=n/2-1))return 0;
a[n]=n/2+1,b[n]=n/2-1;
}
auto chk=[=](int x,int y)-> bool {
if(x+y<=20)return x>10;
return x-y>1;
};
f[0]=1,g[0]=0;
for(int i=1,j=0;i<=n;++i){
if(a[i]==-1)continue;
if(a[i]+b[i]!=i)return 0;
if(chk(a[i],b[i])&&i<n)return 0;
f[i]=(f[j]*F(a[j],b[j],a[i],b[i])+g[j]*F(b[j],a[j],a[i],b[i]))%mod;
if(a[i]!=b[i])g[i]=(f[j]*F(a[j],b[j],b[i],a[i])+g[j]*F(b[j],a[j],b[i],a[i]))%mod;
j=i;
}
return (f[n]+g[n])%mod;
}
int main(){
init();
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)cout<<solve()<<'\n';
}
J. 辞甲猾扎
题意:树上有一些黑点,你可以选一些未染色的点染白。白点和黑点会一起扩散,每次扩散一条边。
一个点如果仅被白点或黑点扩散则它会变成对应颜色,如果同时被白点和黑点扩散则会变成白色。
求使黑点完全无法扩散最少要染多少个白点。\(n\leq 10^6\)。
本质上要求染色的点“占领”了所有与黑点相邻的点(但不需要占领黑点)。
这里“占领”指重合或相邻。
这是经典的“点覆盖点”问题。
树形dp,\(f_{u,0/1/2}\) 表示 \(u\) 自己被染色/被儿子占领/被父亲占领,且子树全部被占领,子树内需染色的最少点数。转移为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
const int N=1e6+5,inf=1e9;
int n,x,y,f[N],g[N],h[N];
bool a[N],b[N];
vector<int> e[N];
inline void adde(int x,int y){e[x].push_back(y);}
void dfs(int u,int fa){
f[u]=1;
if(b[u])f[u]=inf;
if(b[fa])h[u]=inf;
int tmp=inf;
for(int v:e[u])if(v^fa){
dfs(v,u);
int t=min(f[v],g[v]),tt=min(t,h[v]);
f[u]+=tt;
g[u]+=t,h[u]+=t;
tmp=min(tmp,f[v]-g[v]);
}
if(tmp>0&&a[u])g[u]+=tmp;
}
int k;
vector<int> p;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>k;
p.resize(k);
for(int& x:p)cin>>x,b[x]=1;
for(int i=1;i<n;++i)cin>>x>>y,adde(x,y),adde(y,x);
for(int x:p)for(int y:e[x])if(!b[y])a[y]=1;
dfs(1,0);
cout<<min(f[1],g[1])<<'\n';
}
D. 摊位分配
题意:有 \(n\) 个社团,\(m\) 个摊位。第 \(i\) 个社团的贡献值为 \(u_i\)。摊位分配方法为在集合 \(\{2^{-j}u_i, 1\leq i\leq n, 0\leq j\leq m-1\}\) 中取出最大的 \(m\) 个值,比较规则为:
- 优先取 \(u_i\) 大的;
- 如相等,优先取未分配的;
- 如还相等,优先取初始 \(u_i\) 大的;
- 如还相等,优先取 \(i\) 小的。
然后把摊位分配给相应的社团。问最终每个社团能分到多少摊位。\(n\leq 10^5, m\leq 10^9\)。
题目中的方案等价于,一个社团分到一个摊位之后就把它的贡献除 2 再去和剩下的比。
注意到至多 \(n\log w\) 次分配之后,所有社团的贡献值都会落在一个 \([x,2x)\) 的区间。因此它们会依次得到一个摊位然后循环。因此实际上只需算前 \(n\log w\) 次分配即可。
可以用优先队列维护这个过程。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
const int N=1e5+5;
int n,m,q,ans[N];
struct node{
double u;
int id,ans;
bool operator < (const node& p)const{
if(u!=p.u)return u<p.u;
if(!ans)return 0;
if(!p.ans)return 1;
return id>p.id;
}
}a[N];
int id[N];
bool cmp(node a,node b){
return a.u>b.u||a.u==b.u&&a.id<b.id;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m;
if(m>n*30)q=(m-n*30)/n,m-=q*n;
for(int i=1;i<=n;++i)cin>>a[i].u,a[i].id=i;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;++i)id[a[i].id]=i,a[i].id=i;
priority_queue<node> pq;
for(int i=1;i<=n;++i)pq.push(a[i]);
for(int i=1;i<=m;++i){
node u=pq.top();
pq.pop();
++u.ans,++ans[u.id],u.u/=2;
pq.push(u);
}
for(int i=1;i<=n;++i)cout<<ans[id[i]]+q<<' ';
cout<<'\n';
}
G. Imyourfan
题意:初始有一个仅含 WMX
三个字符的字符串。WM 两人轮流操作,每个人可以找一个不含 X
的子区间删去。若某次操作后
- 所有
W
都被删去:W 胜; - 所有 'M' 都被删去:M 胜;
- 所有
WM
都被删去:平局。
求最优决策下的结果。\(|s|\leq 10^5\)。
把原串按 X
分隔开,每一段是独立的。
对每个不含 X
的段,单独考虑:
- 如果
M
的段数比W
多(等价于首尾都是M
),则 W 必胜; - 如果
W
的段数比M
多(等价于首尾都是W
),则 M 必胜; - 如果段数一样多,则谁先开始取谁必胜。
因为 W 先取,所以当 W 必胜段数大于等于 M 必胜段数时,W 占优;反之 M 占优。
当 W 必胜段数等于 M 必胜段数时,有一种特殊情况可以使 M 达到平局:就是取到最后只剩一段,这段是 W 必胜且不是纯 M 段。此时 M 可以一次性全部取完。
M 必胜段数等于 W 必胜段数 +1 时同理。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
void solve(){
string s;
cin>>s;
int n=s.length();
int cnt1=0,cnt2=0,cnt3=0,cnt4=0,cnt0=0;
for(int i=0,o=0,l=0;i<=n;++i){
if(i==n||s[i]=='X'){
if(o>0){
if(l==1)++cnt1;
else ++cnt3;
}
else if(o<0){
if(l==1)++cnt2;
else ++cnt4;
}
else ++cnt0;
o=l=0;
}
else if(s[i]=='W'){
if(i==0||s[i-1]!='W')++o,++l;
}
else if(s[i]=='M'){
if(i==0||s[i-1]!='M')--o,++l;
}
}
int d=cnt1+cnt3-cnt2-cnt4;
if(d<0)cout<<"Water\n";
else if(d>1)cout<<"Menji\n";
else if(d==0){
if(cnt4>0)cout<<"Draw\n";
else cout<<"Water\n";
}
else if(d==1){
if(cnt3>0)cout<<"Draw\n";
else cout<<"Menji\n";
}
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}