1.CF1852A Ntarsis' Set
题意

多组测试数据 1 ≤ T ≤ 10 1\le T\le 10 1≤T≤10, 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1≤n≤105, 1 ≤ k ≤ 1 0 9 1\le k\le 10^9 1≤k≤109,保证 a i a_i ai 单调递增, a i ∈ [ 1 , 1 0 9 ] a_i\in[1,10^9] ai∈[1,109]。
思路
赛时写了一个很奇妙的做法。首先若 a 1 ≠ 1 a_1\neq 1 a1=1,答案必然是 1 1 1。于是只讨论 a 1 = 1 a_1=1 a1=1 的情况。
即每次都是先删除 S S S 最小的数,题意转化为 k + 1 k+1 k+1 天要删的第 1 1 1 个数。考虑研究每天删去的第一个数有什么性质。那就拿一个样例:
5 6
1 2 6 8 11
每次删去:
1 2 6 8 11
3 4 10 13 16
5 7 15 18 21
9 12 20 23 26
14 17 25 28 31
19 22 30 33 36
24(ans)
我们发现,删到一定程度,相邻天数总是相差 n = 5 n=5 n=5。这是为何?
-
首先第 11 11 11 大总是跑在前面且差 5 5 5,因为实时的第 11 11 11 小,前面已经删掉了 5 5 5 个数(包括自己,下同),所以第 11 11 11 大会一直往后推 5 5 5。
-
对于第 8 8 8 小:
- 前面删去 4 4 4 个数,按说是 8 + 4 = 12 8+4=12 8+4=12,但是前面又删一个 11 11 11,于是还要 12 + 1 = 13 12+1=13 12+1=13。
- 13 13 13 后面亦然重复这个 + 4 + 1 = + 5 +4+1=+5 +4+1=+5 的过程。
-
对于第 6 6 6 小:
- 前面删去 3 3 3 个数,于是 6 + 3 = 9 6+3=9 6+3=9,前面又有 8 8 8 删去,于是 9 + 1 = 10 9+1=10 9+1=10;
- 第三天前面又删 3 3 3 个数, 10 + 3 = 13 10+3=13 10+3=13,一是前面有个 11 11 11 已经删掉,二是 13 13 13 在第二天的第 8 8 8 大被删去。于是 13 + 1 + 1 = 15 13+1+1=15 13+1+1=15。
- 后面亦然重复这个 + 3 + 1 + 1 = + 5 +3+1+1=+5 +3+1+1=+5 的过程。
-
以此类推,推导第 1 1 1 小:
- 1 1 1 前面 1 1 1 个数被删了, 1 + 1 = 2 1+1=2 1+1=2 有占位, 2 + 1 = 3 2+1=3 2+1=3 结束第二天;
- 前面又被删掉 1 1 1 个数, 3 + 1 = 4 3+1=4 3+1=4 但是 4 4 4 被删过于是 4 + 1 = 5 4+1=5 4+1=5;
- 5 + 1 = 6 5+1=6 5+1=6 但是 6 6 6 被删过了,后面 7 , 8 7,8 7,8 都被删过了,于是 6 → 9 6\to 9 6→9;
- 后面 10 ∼ 13 10\sim 13 10∼13 都被删过了于是 9 → 14 9\to 14 9→14,亦然重复这个 + 1 + 1 + 1 + 1 + 1 = 5 +1+1+1+1+1=5 +1+1+1+1+1=5 的过程。
考虑这个"删过"的本质?就是 a 2 ∼ n a_{2\sim n} a2∼n 先一步到达了某些数,导致"删过";而 a 1 = 1 a_1=1 a1=1 的增速最慢,于是前面总是 n n n 个数被删了(后面的数也会 + 5 +5 +5,于是形成某种周期),下一次就要删 + 5 +5 +5 之后的数了。
考虑加速这个过程。设删过 p o s pos pos 个数,一开始 c u r = 1 cur=1 cur=1,若 c u r + p o s ≥ a p o s + 1 cur+pos\ge a_{pos+1} cur+pos≥apos+1,说明跑到一个被删过的数,于是 p o s ← p o s + 1 pos\leftarrow pos+1 pos←pos+1,直到 c u r + p o s < a p o s + 1 cur+pos<a_{pos+1} cur+pos<apos+1。前面删过多少数是可以继承的。直到 p o s = n pos=n pos=n 时,前面就总是删过 n n n 个数了。记录这样的 + p o s +pos +pos 进行了 t i c k tick tick 轮,若 t i c k < k tick<k tick<k 就可以直接 break 输出答案;否则答案再加 ( k − t i c k ) × n (k-tick)\times n (k−tick)×n。
时间复杂度 ≤ O ( n ) \le O(n) ≤O(n)。CF 218ms。
他们都夸我注意力惊人怎么办?还是推荐一篇严谨一点的 O ( n ) O(n) O(n) 做法。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9,inf=1e18;
ll id,Q,n,k;
ll a[N];
int main()
{
freopen("superset.in","r",stdin);
freopen("superset.out","w",stdout);
scanf("%lld%lld",&id,&Q);
while(Q--)
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
if(a[1]!=1)
{
puts("1");
continue;
}
//a[1]=1
ll cur=1,pos=1,tick=0;
a[n+1]=inf;
while(1)
{
while(pos<=n)
{
if(a[pos]<cur+pos&&cur+pos<a[pos+1])break;
pos++;
}
cur+=pos;
tick++;
if(tick==k)break;
if(pos==n)break;
}
if(tick==k)
{
printf("%lld\n",cur);
continue;
}
cur+=(k-tick)*n;
printf("%lld\n",cur);
}
return 0;
}
2.CF1936C Pokémon Arena
题意

多组测试数据 1 ≤ T ≤ 5 1\le T\le 5 1≤T≤5, 1 ≤ n × m ≤ 2 × 1 0 5 1\le n\times m\le 2\times 10^5 1≤n×m≤2×105。
思路
赛时觉得还算可做,但是没有往建模去想。于是去后面拼暴力了。
首先对于三只宝可梦 x , y , z x,y,z x,y,z 的同一个属性 j j j,有 a x , j > a y , j > a z , j a_{x,j}>a_{y,j}>a_{z,j} ax,j>ay,j>az,j 并且 x x x 在擂台。雇佣依次雇佣三只宝可梦并充能,费用是 ( a x , j − a y , j + c y ) + ( a y , j − a z , j + c z ) = a x , j − a z , j + c y + c z (a_{x,j}-a_{y,j}+c_y)+(a_{y,j}-a_{z,j}+c_z)=a_{x,j}-a_{z,j}+c_y+c_z (ax,j−ay,j+cy)+(ay,j−az,j+cz)=ax,j−az,j+cy+cz。但显然直接雇佣宝可梦 z z z 给它充能,只用 a x , j − a z , j + c z a_{x,j}-a_{z,j}+c_z ax,j−az,j+cz。
发现要打败 x x x 的某个性质,只雇佣一只来搞是更优的。
到这里发现宝可梦和性质特别多,于是可以考虑建模。剩下的交给 dijkstra 就好了。
对于任意一对点 ( x , y ) (x,y) (x,y),首先 x x x 连向 m m m 个性质点 x 1 ∼ m x_{1\sim m} x1∼m 然后 x j x_{j} xj 和 y j y_{j} yj 之间双向边,边权分别是给 x , y x,y x,y 的 j j j 性质充能所用的价值,即 x j → y j : max ( a y , j − a x , j , 0 ) , y j → x j : max ( a x , j − a y , j , 0 ) x_j\to y_j:\max(a_{y,j}-a_{x,j},0),y_j\to x_j:\max(a_{x,j}-a_{y,j},0) xj→yj:max(ay,j−ax,j,0),yj→xj:max(ax,j−ay,j,0)。
但是挂着个雇佣费怎么整?我们直接令 x x x 的性质点出去 x o u t x_{out} xout 花费 c x c_x cx 即可。
但是建图规模来到 n 2 m n^2m n2m。我们发现有个 max ( a y , j − a x , j , 0 ) \max(a_{y,j}-a_{x,j},0) max(ay,j−ax,j,0)。有的边必然边权为 0 0 0。于是 n n n 个点的 j j j 属性排序,连成一条链,这相当于把一些边权为 0 0 0 的边略去。于是 x j x_j xj 充能到 y j y_j yj 的费用就直接在链上走过去即可(这里巧妙利用了绝对值的性质)。
然后跑 1 → n 1\to n 1→n 最短路即可。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pll pair<ll,ll>
#define fi first
#define se second
#define mk make_pair
const ll N=4e5+9,M=6e5+9,inf=1e18;
ll id,Q,n,m;
ll c[N];
vector<ll>a[N];
struct edge
{
ll to,next,w;
}e[M<<1];
ll idx,head[M];
void addedge(ll u,ll v,ll w)
{
idx++;
e[idx].to=v;
e[idx].next=head[u];
e[idx].w=w;
head[u]=idx;
}
ll dis[M];
bool vis[M];
pll b[N];
void dijkstra(ll s)
{
for(int i=0;i<=n+n*m;i++)
{
dis[i]=inf;
vis[i]=0;
}
priority_queue<pll>q;
dis[s]=0;
q.push(mk(0,s));
while(!q.empty())
{
pll tem=q.top();
q.pop();
ll u=tem.se;
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
ll v=e[i].to,w=e[i].w;
if(dis[u]+w<dis[v])
{
dis[v]=dis[u]+w;
q.push(mk(-dis[v],v));
}
}
}
}
int main()
{
freopen("pokemon.in","r",stdin);
freopen("pokemon.out","w",stdout);
scanf("%lld%lld",&id,&Q);
while(Q--)
{
idx=0;
memset(head,0,sizeof(head));
memset(e,0,sizeof(e));
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&c[i]);
for(int j=1;j<=m;j++)
{
addedge(i,i+j*n,0);
addedge(i+j*n,i,c[i]);
}
}
for(int i=1;i<=n;i++)
{
a[i].resize(m+1);
for(int j=1;j<=m;j++)
scanf("%lld",&a[i][j]);
}
for(int j=1;j<=m;j++)
{
for(int i=1;i<=n;i++)
b[i]=mk(a[i][j],i);
sort(b+1,b+n+1);
for(int i=1;i<n;i++)
{
addedge(b[i].se+j*n,b[i+1].se+j*n,0);
addedge(b[i+1].se+j*n,b[i].se+j*n,b[i+1].fi-b[i].fi);
}
}
dijkstra(1);
printf("%lld\n",dis[n]);
}
return 0;
}
反思
没有敢于去建模啊,不过这题建模还是太厉害了,感觉是图论建模的 trick 见少了。同时也没有在赛时分析一些可行的性质......
3.CF983E NN country
nflsoi 也考过一次,这次测试的 D . D. D. 题。
待补。
4.AT_code_festival_2017_qualc_f Three Gluttons
接下来是 GJOI 10.22,来到 wyx 出题 round。
1.洛谷 P9464 EGOI2023 Padel Prize Pursuit / 追梦笼式网球
题意
题目传送门添加链接描述。建议前往原题阅读题干、样例以及其解释。
思路
注意奖牌持有天数可以叠加。
考虑模拟第 i i i 天的胜者拿到奖牌 i i i,奖牌 i i i 在各个选手之间是怎么游走了。
第 i i i 天胜者 x i x_i xi,在第 i i i 天之后的第 j j j 天,是 x i = y j x_i=y_j xi=yj 赢之后第一次输,奖牌全部给了 y i y_i yi。
于是考虑开 2 m 2m 2m 个点,每天 x i ← y i x_i\leftarrow y_i xi←yi 连边, x i → y j x_i\to y_j xi→yj 连边, y j y_j yj 的含义如上所述。如果 x i x_i xi 赢了后面没有输过,就单独开一个虚拟节点 e n d end end, x i → e n d x_i\to end xi→end。

对于第 2 2 2 个样例如此建图。而若把胜点和败点合并,就会变成一棵 m + 1 m+1 m+1 个节点的树,并且将边权转化为点权。
这个树保证了节点对应的胜点,浅处胜利时间大于深处胜利时间,即将把天数 i i i 作为节点编号,在天数 i i i 拿到奖牌 i i i,奖牌 i i i 在各个人之间的游走,就是从 i → e n d i\to end i→end 途径过的人,每个人持有的时间就是途径的人各自的点权和。在这里插入代码片
于是从 e n d end end 开始 dfs,维护 e n d end end 到 i i i 路径上,维护每个人的点权和,就是 i i i 奖牌被每个人拿的天数。
考虑维护持有天数最多的人 m a ma ma,加入奖牌 i i i 和最开始拿着 i i i 的 u u u,不难发现更新之后的 m a ′ ma' ma′ 要么是 i i i 要么是 m a ma ma。根据各自点权和以及编号大小更新即可。
时间复杂度 O ( n ) O(n) O(n)。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll n,m;
struct term
{
ll x,y;
}p[N];
map<ll,ll>pos,pre;
ll ANS[N],val[N];
vector<ll>G[N],Cnt;
void dfs(ll u,ll ma)
{
for(auto v:G[u])
{
ll id=pos[v];
Cnt[id]+=val[v];
if(Cnt[id]>Cnt[ma])
{
ANS[id]++;
dfs(v,id);
}
else if(Cnt[id]==Cnt[ma])
{
ll z=min(ma,id);
ANS[z]++;
dfs(v,z);
}
else
{
ANS[ma]++;
dfs(v,ma);
}
Cnt[id]-=val[v];
}
}
int main()
{
freopen("ybt.in","r",stdin);
freopen("ybt.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
ll x,y;
scanf("%lld%lld",&x,&y);
p[i]=(term){x+1,y+1};
}
ll en=m+1;
for(int i=m;i>=1;i--)//倒插更方便查询i天赢得人在哪一天第一次输
{
if(!pre[p[i].x])
{
G[en].push_back(i);
val[i]=m+1-i;
}
else
{
ll z=pre[p[i].x];
G[z].push_back(i);
val[i]=z-i;
}
pos[i]=p[i].x;
pre[p[i].y]=i;
}
Cnt.resize(n+3);
dfs(en,n+1);
for(int i=1;i<=n;i++)
printf("%lld ",ANS[i]);
return 0;
}
2.CF1310D Tourism
题意

n ≤ 80 , k ≤ 10 n\le 80,k\le 10 n≤80,k≤10。
思路
长度为 k + 1 k+1 k+1 的游走序列 p p p,其中 p 1 = p k + 1 = 1 p_1=p_{k+1}=1 p1=pk+1=1。
一开始想要 2 ∼ k 2\sim k 2∼k 全部枚举,然后判断不合法的情况,计算上一次出现的下标 p r e x pre_x prex,当前下标 i i i 能选 x x x 当且仅当 i − p r e x i-pre_x i−prex 为偶数------这个必然跑满。
但是这个是可以优化的。我们直接考虑合法情况,以及折半搜索。
我们直接枚举 p 3 , p 5 , . . . , p k − 1 p_3,p_5,...,p_{k-1} p3,p5,...,pk−1( k k k 是偶数,这里枚举奇数位),这里可以随便枚举。然后考虑填充偶数位,要求偶数位的数不能在奇数位出现。
我们可以类似 Floyd 的思路,预处理点 x , z x,z x,z 之间, d i s x , y + d i s y , z dis_{x,y}+dis_{y,z} disx,y+disy,z 最小的最佳途经点 y y y,要求 y ≠ x , y ≠ z y\neq x,y\neq z y=x,y=z。为了防止 p 1 , p 3 , p 5 , . . . , p k − 1 p_1,p_3,p_5,...,p_{k-1} p1,p3,p5,...,pk−1 都是不同的极端占用情况,至少要预处理出两个点之间 5 5 5 组优解。
时间复杂度 O ( n k 2 − 1 ) O(n^{\frac{k}{2}-1}) O(n2k−1)。
代码
cpp
#pragma GCC optimise(2)
#pragma GCC optimise(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=82,inf=1e18,mod=1e9+7;
ll n,k;
ll G[N][N];
ll pass[N][N][7];
ll a[N],vis[N];
ll ans=inf;
ll dis(ll i,ll j,ll k)
{
if(k==0)return inf;
return G[i][k]+G[k][j];
}
void dfs(ll id)//决定奇数位4个
{
if(id>k/2)
{
ll ret=0;
for(int i=1;i<=k/2;i++)
{
ll nxt=i+1;
if(nxt>k/2)nxt=1;
for(int t=0;t<=5;t++)
{
if(!vis[pass[a[i]][a[nxt]][t]])
{
ret+=dis(a[i],a[nxt],pass[a[i]][a[nxt]][t]);
break;
}
}
}
ans=min(ans,ret);
return;
}
for(int i=1;i<=n;i++)
{
a[id]=i;
vis[i]++;
dfs(id+1);
a[id]=-1;
vis[i]--;
}
}
int main()
{
// freopen("luogu.in","r",stdin);
// freopen("luogu.out","w",stdout);
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lld",&G[i][j]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
for(int k=1;k<=n;k++)
{
if(k==i||k==j)continue;
pass[i][j][6]=k;
for(int t=5;t>=0;t--)//前6优的途经点pass (0~5)
{
if(dis(i,j,pass[i][j][t+1])<dis(i,j,pass[i][j][t]))swap(pass[i][j][t+1],pass[i][j][t]);
else break;
}
}
}
}
a[1]=1;
vis[1]=1;
dfs(2);
printf("%lld",ans);
return 0;
}
3.P3203 HNOI2010 弹飞绵羊
我的博客。