( ext{Encounter and Farewell})
题目
解法
写一写格雷码的做法,免得我忘了。
首先明确格雷码有 (n) 位,(1) 相当于 “使用插入到对应位的原数”。之前已证明插入的原数集与线性基是一样的。
那么一共有 (2^n) 种格雷码,也就是 (2^n) 个不同的数(如果有相同的数,意味着某个原数并不会被插入,矛盾)。同时也可以保证没有环,解释和上文相同。
( ext{CodeForces - 1503D Flip the Cards})
题目
解法
攷,想了好久才懂。
考虑用两个栈维护单减子序列(即 (f[i]))。首先考虑什么时候可以不管栈顶元素地放置:
这时 (i+1) 号元素可以不管栈顶元素。我们可以将序列按照这个条件划分成多个段(比如这个情况就是 (i+1) 变成新段起始点),容易发现每个段之间互不干扰。
那每个段之间该如何选取?首先贪心地选栈顶元素最小的栈加入当前元素是最可能有解的,但是这并不一定是最优的。
真的不是最优的吗?我们可以分析上文的条件,由于 (min_{jle i-1}f[j]<max_{jge i} f[j]),首先我们可以排除 (f[i]<f[i-1]),这样前缀 (min) 就不会因为是否包含 (i) 而改变,而 (i) 是否加入后半部分对符号产生了改变,说明 (f[i]) 是一个后缀 (max)!虽然段中可能有比 $f[i] $ 更大的值,手玩一下可以发现符合这个限制确实每次都贪心地选,不然 (f[i]) 可能无法插入。
( ext{CodeForces - 1500B Two chandeliers})
题目
解法
由于序列中每个数互异,我们可以直接处理每对相同的数经过多少会坐标相同,设它们的初始位置为 (x,y),那么就相当于解一个方程组 (x+k_1n=y+k_2m),用扩欧即可,注意保证 (k_1,k_2) 为最小正整数解。
然后二分即可。
代码
气人,不想调了,就是一道 (mathtt{SB}) 题。
(mathtt{Update on 2021.5.12}):破案了,有两个量 (a,b) 加上同一个量,但是我更改的 (a) 会影响那个量。以后这样的情况都用 ( m temp) 来存了,血的教训!!!
气人,不想调了,我怎么 (mathtt T) 了???而且为什么优化越多 (mathtt T) 得越多???
嗷嗷嗷我卡过去了!!!现在不开 ( m O(2)) 也能随便过嘿嘿嘿。就是预处理商和余数减低除法次数。
然后发现别人都是 (7 m s) 随便过?嘤嘤嘤?
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
typedef long long ll;
const int maxn=1e6+5;
int n,m,num[maxn][2],vis[maxn];
ll k,cnt[maxn],Delta,ans[maxn],R[maxn];
ll exgcd(int a,int b,ll &x,ll &y) {
if(!b) {
x=1,y=0;
return a;
}
int g=exgcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
inline ll Get(ll mid) {
ll ret=mid,p=mid/Delta,q=mid%Delta;
for(register int i=1;i<=n;++i) {
ret=(ret-((!(~vis[num[i][0]]) && mid>=cnt[num[i][0]]*n+i)?(q<R[i]?p-ans[i]-1:p-ans[i])+1:0));
}
return ret;
}
int main() {
n=read(9),m=read(9),k=read(9ll);
ll xx,yy; ll G=exgcd(n,m,xx,yy);
for(register int i=1;i<=n;++i) num[i][0]=read(9);
for(register int i=1;i<=m;++i) num[i][1]=read(9);
Delta=n/G*m;
ll den=Delta/n,dem=Delta/m;
for(register int i=1;i<=n;++i) cnt[num[i][0]]=i,vis[num[i][0]]=1;
for(register int i=1;i<=m;++i) {
int x=num[i][1];
if(vis[x]==1) {
int a=cnt[x],b=i;
if((a-b)%G!=0) continue;
ll X=((xx^-1)+1)*((a-b)/G),Y=yy*((a-b)/G);
if(X>=den) Y=Y-(X/den)*dem,X=X-(X/den)*den;
if(Y>=dem) X=X-(Y/dem)*den,Y=Y-(Y/dem)*dem;
if(X<0) Y=Y+((den-X-1)/den)*dem,X=X+((den-X-1)/den)*den;
if(Y<0) X=X+((dem-Y-1)/dem)*den,Y=Y+((dem-Y-1)/dem)*dem;
cnt[x]=X; vis[x]=-1;
}
}
for(register int i=1;i<=n;++i)
ans[i]=(cnt[num[i][0]]*n+i)/Delta,R[i]=(cnt[num[i][0]]*n+i)%Delta;
ll l=1,r=1e18,mid;
while(l<r) {
mid=l+r>>1;
if(Get(mid)<k) l=mid+1;
else r=mid;
}
print(l,'
');
return 0;
}
( ext{CodeForces - 1519F Chests and Keys})
题目
解法
想象我们是一个暴力更新的过程。可以钦定一个更新的顺序:顺序遍历钥匙,对于每把钥匙,规定 从小到大枚举宝箱,再枚举这个宝箱给这把钥匙的流量。由于枚举了流量就可以直接转移到下一个宝箱了,如果宝箱枚举到 (n),我们就转移到下一把钥匙上。
( ext{CodeForces - 1521D Nastia Plays with a Tree})
题目
解法
容易发现答案就是将树划分成多条链再顺次连接,需要最小化割的边数或最大化链的条数。
方案一
从下往上割边,设当前点为 (u)。如果 ( ext{deg}_uge 2) 就断掉和父亲的边,这样父亲可以连其他儿子。
代码
方案一
#include <bits/stdc++.h>
using namespace std;
#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)
#define debug(...) do {cerr<<__LINE__<<" : ("#__VA_ARGS__<<") = "; Out(__VA_ARGS__); cerr<<flush;} while(0)
template <typename T> void Out(T x) {cerr<<x<<"
";}
template <typename T,typename ...I> void Out(T x,I ...NEXT) {cerr<<x<<", "; Out(NEXT...);}
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}
const int maxn=1e5+5;
int n,cho[maxn][3],ep[maxn][2],dp[maxn];
vector <int> g[maxn];
/*
cho[i][0/1]: 选择的两个点(当然也可能只有一个)
ep[i][0/1]: 链的底部
*/
void dfs(int u,int fa) {
for(int i=0;i<g[u].size();++i) {
int v=g[u][i];
if(v==fa) continue;
dfs(v,u); dp[u]+=dp[v];
if(cho[v][1]) continue; // 儿子已经形成完整的链
rep(j,0,2) if(!cho[u][j] || j==2) {
cho[u][j]=v;
dp[u]+=(j&2)>>1;
break;
}
// 在超过两条边后割掉所有儿子边
}
if(fa && cho[u][1]) ++dp[u];
}
void fuck(int u,int fa) {
ep[u][0]=ep[u][1]=u;
rep(i,0,1) if(cho[u][i]) {
fuck(cho[u][i],u);
ep[u][i]=ep[cho[u][i]][0];
}
for(int i=0;i<g[u].size();++i) {
int v=g[u][i];
if(v==fa || v==cho[u][0] || v==cho[u][1]) continue;
fuck(v,u);
printf("%d %d %d %d
",u,v,ep[u][0],ep[v][1]);
ep[u][0]=ep[v][0];
}
}
int main() {
for(int T=read(9);T;--T) {
n=read(9);
memset(dp,0,sizeof dp);
memset(cho,0,sizeof cho);
rep(i,1,n) g[i].clear();
rep(i,1,n-1) {
int a=read(9),b=read(9);
g[a].push_back(b),g[b].push_back(a);
}
dfs(1,0); print(dp[1],'
'); fuck(1,0);
}
return 0;
}
( ext{CodeForces - 1486F Pairs of Paths})
题目
解法
还是写一写,理理思路。
分开处理两种情况。
首先题目要求路径只有一个公共点(设它为 (o)),我们发现一种情况是否合法只和端点与 (o) 的路径上离 (o) 最近的点是否相同有关。由图,(o) 点实际上是两条路径 ( m lca) 中的一个。
为了方便,我们将一条路径表示成一个五元组:((x,y,a,b, ext{lca}))。其中 (a,b) 分别是端点 (x,y) 与 ( m lca) 的路径上离 ( m lca) 最近的点。需要注意的是,如果有 (x= ext{lca}),需要将 (a) 赋为一个新数(使用从 (n+1) 开始的计数器),因为实际上在 (o) 点相交的路径是合法的。
处理第一种情况。显然要在 ( ext{lca}=o) 的五元组基础上统计(注意这里的五元组范围都在这一段),为了统计方便,我们令 (a<b)(如果不保证就不只有 (a_i e a_j,b_i e b_j) 的限制了)。这时你可以以 (a) 为关键字从大到小排序或以 (b) 为关键字从小到大排序。以以 (a) 为关键字为例,我们提取出一段 (a) 相同的五元组,那么只要在这之前的五元组都可以和这一段五元组匹配,就消除了 (a) 互异的限制,而对于 (b),我们开一个桶来存之前的五元组有多少个 (b_i=b),这是不能选的。
处理第二种情况。我们肯定没法枚举图中的 ((A_2,B_2)) 链,那就枚举 ((A_1,B_1)) 呗!将五元组按 ( m lca) 深度排序,继续在 ( ext{lca}=o) 的五元组基础上统计(注意这里的五元组范围包含所有已统计的五元组)。由于保证之前段的五元组的 ( ext{lca}') 与这个 ( m lca) 互异且 ( ext{dep}_{ ext{lca}'}le ext{dep}_{ ext{lca}}),五元组的 端点 只在 ( m lca) 的子树出现一次,所以直接统计 ( m lca) 子树内个数即可,用树状数组维护。
时间复杂度 (mathcal O(nlog n)),但是据说有 (mathcal O(n)) 的做法,不会。
( ext{CodeForces - 1500C Matrix Sorting})
题目
解法
对于每行增加一个关键字用于维护 ( m stable) 排序。
注意到,只要我们保证 (b) 中相邻行的相对位置即可,而且两行的相对位置只和最后一次对于它们的排序有关。
可以考虑倒推。
如果后面的操作使某对相对关系成立,那么前面这对相对关系就可以任意进行操作。
将相邻两行和每一列的操作都对应成一个点,有操作使行满足目标顺序,则操作向行连边。有操作使行不满足目标顺序,则行向操作连边。当一个操作没有入度时才可以使用,这说明它影响的相对关系都已经成立了,当行入度减少 (1) 时就可以使用。
最后查看能否用 (b) 对应的关键字排序即可。
需要注意的是在类 ( m topol) 中行不能作为起始点,因为它的入度为 (0) 代表它不能被满足。
( ext{AtCoder - arc119E Pancakes})
题目
解法
神仙结论题。
首先题意就是计算 (Delta =max{|a_i-a_{i-1}|+|a_j-a_{j+1}|-|a_{i-1}-a_j|-|a_{j+1}-a_i|})。
朴素的想法就是找出所有 ((a_i,a_{i+1})) 这样的二元组然后两两匹配求出 (Delta),但是这样显然超时。
这里有个结论,答案只在相同大小关系的二元组之中,定义 (a_i<a_{i+1},a_j<a_{j+1}) 时 ((a_i,a_{i+1})) 与 ((a_j,a_{j+1})) 是相同大小关系的二元组。
(mathtt{How to prove it?})
令 ((a_i,a_{i+1})) 与 ((a_j,a_{j+1})) 不是相同大小关系的二元组,将它们依次用 (x,y,z,w) 代替。
我们讨论其中一种情况(另一种情况类似):(x<y,z>w)。那么初始时这两对二元组总贡献为 (y-x+z-w)。再进行分类讨论:
- (min{y,z}>max{x,w})。这对 (Delta) 没有改变。
- 满足 (z>x) 与 (y>w) 中的其中一个条件。你会发现 (Delta) 恒为负。容易发现不满足条件的那一组都会变号,实际上 (Delta=2(z-x))(当不满足 (z>x)) 或 (Delta=2(y-w))(当不满足 (y>w))。
- 不满足 (z>x) 与 (y>w) 中的任何一个条件。这种情况是不存在的。
证毕。
代码
( ext{CodeForces - 1525E Assimilation IV})
题目
解法
说一下统计答案。
距离为 (n + 1) 的城市可以放在任何位置,距离为 (n) 的城市不可以放在第一个位置,距离为 (n-1) 的城市不可以放在前两个位置 …
所以对于第一个位置,统计出距离为 (n+1) 的城市来放置,对于第二个位置,统计出距离大于 (n-1) 的城市来放置 …
将每种位置放置城市数相乘就是我们的不合法方案数。需要注意的是,对于第 (i) 个位置需要将放置城市数 (-(i-1)) 再相乘,因为前面选择的城市必定包含在第 (i) 个位置放置城市数内。
( ext{CodeForces - 1528C Trees of Tranquillity})
题目
代码
需要注意的是,两个点之间的 (l,r) 只有可能是 包含 / 不交 的关系。
记一个比较强的 (mathtt {set}) 实现:
- 在 (mathtt {set}) 中查询大于 ((l_u,0)) 的点 (v),它是 (l_v>l_u) 中 (r_v) 最小的点,也即最有可能被包含的点。
- 在 (mathtt {set}) 找 (v) 的前一个点 (p),它是 (l_p<l_u) 中 (r_p) 最大的点,也是最有可能包含 (u) 的点。
- 分别判定是否包含,不包含就加一。若 (p,v) 有包含关系,说明删多了,因为我们已经计算 (p,v) 的冲突只是没有删掉点,所以再加回去。
#include <bits/stdc++.h>
using namespace std;
#define rep(i,_l,_r) for(signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)
#define debug(...) do {cerr<<__LINE__<<" : ("#__VA_ARGS__<<") = "; Out(__VA_ARGS__); cerr<<flush;} while(0)
template <typename T> void Out(T x) {cerr<<x<<"
";}
template <typename T,typename ...I> void Out(T x,I ...NEXT) {cerr<<x<<", "; Out(NEXT...);}
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
template <class T> inline T Swap(T &x,T &y) {x^=y^=x^=y;}
typedef pair <int,int> pii;
const int maxn=3e5+5;
int ans,n,idx,l[maxn],r[maxn],siz;
set <pii> s;
set <pii> :: iterator it,It;
vector <int> e[maxn],E[maxn];
void Dfs(int u) {
l[u]=++idx;
for(int i=0;i<E[u].size();++i) Dfs(E[u][i]);
r[u]=idx;
}
bool In(int i,int j) {
return l[i]<=l[j] && r[j]<=r[i];
}
void dfs(int u) {
int tmp=siz; it=s.lower_bound(make_pair(l[u],0));
if(it!=s.end()) siz+=(In(u,it->second)^1);
if(it!=s.begin()) {
It=it--;
siz+=(In(it->second,u)^1);
if(It!=s.end()) siz-=(In(it->second,It->second)^1);
}
ans=Max(ans,siz);
s.insert(make_pair(l[u],u));
for(int i=0;i<e[u].size();++i) dfs(e[u][i]);
s.erase(make_pair(l[u],u));
siz=tmp;
}
int main() {
rep(T,1,read(9)) {
n=read(9); idx=ans=0; s.clear();
rep(i,1,n) e[i].clear(),E[i].clear();
rep(i,2,n) e[read(9)].push_back(i);
rep(i,2,n) E[read(9)].push_back(i);
Dfs(1),dfs(1);
print(ans+1,'
');
}
return 0;
}
( ext{CodeForces - 1528E Mashtali and Hagh Trees})
题目
注意:(u,v) 为朋友当且仅当有一条 有向路径 在两者之间。
解法
如图,(2) 的子树节点 (8) 不能选新的父节点 (9),这样 ((7,9)) 是不满足条件的。所以树的形态最终呈现为一棵树和另一棵树通过根相连接。
接下来就是计算 (f) 了。想了半天,终于在奥妙重重中发现了它的组合含义!可以转化成将 (j) 个小球装进 (x) 个盒子里,小球相同,盒子不同,盒子可为空。答案就是 ( ext{C}(j+x-1,x-1))。
( ext{CodeForces - 1526C Potions})
题目
解法
本来是道水题,但我在赛上搞了差不多两个小时…… 最后发现不对也已经晚了。
“我真傻,真的。”
如果 (mathtt{dp}) 就是设 (dp_{i,j}) 为前 (i) 瓶药,喝 (j) 瓶的最大健康值。
否则可以用一个 (mathtt{sb}) 贪心:先喝所有的药,被药死的时候吐出对自己伤害最大的药。
还有一种做法:先喝所有无害的药,再依次枚举伤害最小的药喝。具体就是用数据结构维护区间健康值的 (min),初始先插入 (ge 0) 的 (a_i),然后再判断一下某药 (i) 后区间 ([i+1,n]) 的 (min) 与 (a_i) 的大小关系。
再说说自己的贪心。将所有 (a_i<0) 的药按 (a_i) 从大到小,(i) 从大到小排序。然后枚举喝了排序中前 (k) 瓶药,再 (mathcal O(n)) 暴力喝。
看上去很正确?
12
40 -10 30 -46 -17 19 -46 44 -49 39 -12 44
Answer: 11
在这个数据中,我们选择了 -49
而非第一个 -46
。
实际上,喝药 (i) 可能会导致后面更优的药不能喝,而喝药 (j) 并不会影响,且它们可能满足 (a_i>a_j) 的关系。
所以归根到底,应该先保证更优的药。
实际上这并不是我耗费时间最多的那个做法。
其实本质上做法和之前数据结构做法一样,但是我的 实现 有问题。比如喝了药 (i) 后需要 ([1,i-1]) 的耗费都小于某个值。但是令人头大的是,喝了药 (j) 后可能会更改 ([1,i-1]) 的限制。所以这个做法是不可行的。
( ext{CodeForces - 1523D Love-Hate})
题目
解法
容易发现,每个答案都是至少 (lceil frac{n}{2} ceil) 个集合的子集。我们随机一个集合,实际上它不包含答案的概率为 (frac{1}{2}),如果我们随机更多次… 随机不到就是你的问题了。
由于每个集合最多有 (15) 个 (1),这样我们就可以状压了。
话说 (ig( frac{1}{2}ig )^{30}) 的概率被我赶上了两次… 是不是哪里出了点问题?