\(\text{Encounter and Farewell}\)
题目
解法
写一写格雷码的做法,免得我忘了。
首先明确格雷码有 \(n\) 位,\(1\) 相当于 “使用插入到对应位的原数”。之前已证明插入的原数集与线性基是一样的。
那么一共有 \(2^n\) 种格雷码,也就是 \(2^n\) 个不同的数(如果有相同的数,意味着某个原数并不会被插入,矛盾)。同时也可以保证没有环,解释和上文相同。
\(\text{CodeForces - 1503D Flip the Cards}\)
题目
解法
攷,想了好久才懂。
考虑用两个栈维护单减子序列(即 \(f[i]\))。首先考虑什么时候可以不管栈顶元素地放置:
这时 \(i+1\) 号元素可以不管栈顶元素。我们可以将序列按照这个条件划分成多个段(比如这个情况就是 \(i+1\) 变成新段起始点),容易发现每个段之间互不干扰。
那每个段之间该如何选取?首先贪心地选栈顶元素最小的栈加入当前元素是最可能有解的,但是这并不一定是最优的。
真的不是最优的吗?我们可以分析上文的条件,由于 \(\min_{j\le i-1}f[j]<\max_{j\ge i} f[j]\),首先我们可以排除 \(f[i]<f[i-1]\),这样前缀 \(\min\) 就不会因为是否包含 \(i\) 而改变,而 \(i\) 是否加入后半部分对符号产生了改变,说明 \(f[i]\) 是一个后缀 \(\max\)!虽然段中可能有比 $f[i] $ 更大的值,手玩一下可以发现符合这个限制确实每次都贪心地选,不然 \(f[i]\) 可能无法插入。
\(\text{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\) 会影响那个量。以后这样的情况都用 \(\rm temp\) 来存了,血的教训!!!
气人,不想调了,我怎么 \(\mathtt T\) 了???而且为什么优化越多 \(\mathtt T\) 得越多???
嗷嗷嗷我卡过去了!!!现在不开 \(\rm O(2)\) 也能随便过嘿嘿嘿。就是预处理商和余数减低除法次数。
然后发现别人都是 \(7\ \rm 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,'\n');
return 0;
}
\(\text{CodeForces - 1519F Chests and Keys}\)
题目
解法
想象我们是一个暴力更新的过程。可以钦定一个更新的顺序:顺序遍历钥匙,对于每把钥匙,规定 从小到大枚举宝箱,再枚举这个宝箱给这把钥匙的流量。由于枚举了流量就可以直接转移到下一个宝箱了,如果宝箱枚举到 \(n\),我们就转移到下一把钥匙上。
\(\text{CodeForces - 1521D Nastia Plays with a Tree}\)
题目
解法
容易发现答案就是将树划分成多条链再顺次连接,需要最小化割的边数或最大化链的条数。
方案一
从下往上割边,设当前点为 \(u\)。如果 \(\text{deg}_u\ge 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<<"\n";}
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\n",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],'\n'); fuck(1,0);
}
return 0;
}
\(\text{CodeForces - 1486F Pairs of Paths}\)
题目
解法
还是写一写,理理思路。
分开处理两种情况。
首先题目要求路径只有一个公共点(设它为 \(o\)),我们发现一种情况是否合法只和端点与 \(o\) 的路径上离 \(o\) 最近的点是否相同有关。由图,\(o\) 点实际上是两条路径 \(\rm lca\) 中的一个。
为了方便,我们将一条路径表示成一个五元组:\((x,y,a,b,\text{lca})\)。其中 \(a,b\) 分别是端点 \(x,y\) 与 \(\rm lca\) 的路径上离 \(\rm lca\) 最近的点。需要注意的是,如果有 \(x=\text{lca}\),需要将 \(a\) 赋为一个新数(使用从 \(n+1\) 开始的计数器),因为实际上在 \(o\) 点相交的路径是合法的。
处理第一种情况。显然要在 \(\text{lca}=o\) 的五元组基础上统计(注意这里的五元组范围都在这一段),为了统计方便,我们令 \(a<b\)(如果不保证就不只有 \(a_i\ne a_j,b_i\ne b_j\) 的限制了)。这时你可以以 \(a\) 为关键字从大到小排序或以 \(b\) 为关键字从小到大排序。以以 \(a\) 为关键字为例,我们提取出一段 \(a\) 相同的五元组,那么只要在这之前的五元组都可以和这一段五元组匹配,就消除了 \(a\) 互异的限制,而对于 \(b\),我们开一个桶来存之前的五元组有多少个 \(b_i=b\),这是不能选的。
处理第二种情况。我们肯定没法枚举图中的 \((A_2,B_2)\) 链,那就枚举 \((A_1,B_1)\) 呗!将五元组按 \(\rm lca\) 深度排序,继续在 \(\text{lca}=o\) 的五元组基础上统计(注意这里的五元组范围包含所有已统计的五元组)。由于保证之前段的五元组的 \(\text{lca}'\) 与这个 \(\rm lca\) 互异且 \(\text{dep}_{\text{lca}'}\le \text{dep}_{\text{lca}}\),五元组的 端点 只在 \(\rm lca\) 的子树出现一次,所以直接统计 \(\rm lca\) 子树内个数即可,用树状数组维护。
时间复杂度 \(\mathcal O(n\log n)\),但是据说有 \(\mathcal O(n)\) 的做法,不会。
\(\text{CodeForces - 1500C Matrix Sorting}\)
题目
解法
对于每行增加一个关键字用于维护 \(\rm stable\) 排序。
注意到,只要我们保证 \(b\) 中相邻行的相对位置即可,而且两行的相对位置只和最后一次对于它们的排序有关。
可以考虑倒推。
如果后面的操作使某对相对关系成立,那么前面这对相对关系就可以任意进行操作。
将相邻两行和每一列的操作都对应成一个点,有操作使行满足目标顺序,则操作向行连边。有操作使行不满足目标顺序,则行向操作连边。当一个操作没有入度时才可以使用,这说明它影响的相对关系都已经成立了,当行入度减少 \(1\) 时就可以使用。
最后查看能否用 \(b\) 对应的关键字排序即可。
需要注意的是在类 \(\rm topol\) 中行不能作为起始点,因为它的入度为 \(0\) 代表它不能被满足。
\(\text{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\) 中的任何一个条件。这种情况是不存在的。
证毕。
代码
\(\text{CodeForces - 1525E Assimilation IV}\)
题目
解法
说一下统计答案。
距离为 \(n + 1\) 的城市可以放在任何位置,距离为 \(n\) 的城市不可以放在第一个位置,距离为 \(n-1\) 的城市不可以放在前两个位置 …
所以对于第一个位置,统计出距离为 \(n+1\) 的城市来放置,对于第二个位置,统计出距离大于 \(n-1\) 的城市来放置 …
将每种位置放置城市数相乘就是我们的不合法方案数。需要注意的是,对于第 \(i\) 个位置需要将放置城市数 \(-(i-1)\) 再相乘,因为前面选择的城市必定包含在第 \(i\) 个位置放置城市数内。
\(\text{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<<"\n";}
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,'\n');
}
return 0;
}
\(\text{CodeForces - 1528E Mashtali and Hagh Trees}\)
题目
注意:\(u,v\) 为朋友当且仅当有一条 有向路径 在两者之间。
解法
如图,\(2\) 的子树节点 \(8\) 不能选新的父节点 \(9\),这样 \((7,9)\) 是不满足条件的。所以树的形态最终呈现为一棵树和另一棵树通过根相连接。
接下来就是计算 \(f\) 了。想了半天,终于在奥妙重重中发现了它的组合含义!可以转化成将 \(j\) 个小球装进 \(x\) 个盒子里,小球相同,盒子不同,盒子可为空。答案就是 \(\text{C}(j+x-1,x-1)\)。
\(\text{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]\) 的限制。所以这个做法是不可行的。