\(\text{Encounter and Farewell}\)

题目

传送门

解法

写一写格雷码的做法,免得我忘了。

首先明确格雷码有 \(n\) 位,\(1\) 相当于 “使用插入到对应位的原数”。之前已证明插入的原数集与线性基是一样的。

那么一共有 \(2^n\) 种格雷码,也就是 \(2^n\) 个不同的数(如果有相同的数,意味着某个原数并不会被插入,矛盾)。同时也可以保证没有环,解释和上文相同。

\(\text{CodeForces - 1503D Flip the Cards}\)

题目

传送门

解法

攷,想了好久才懂。

证明 \(a_i,b_i\le n\) 无解。

考虑用两个栈维护单减子序列(即 \(f[i]\))。首先考虑什么时候可以不管栈顶元素地放置:

\[\min_{j\le i}f[j]>\max_{j>i} f[j] \]

这时 \(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\)。再进行分类讨论:

  1. \(\min\{y,z\}>\max\{x,w\}\)。这对 \(\Delta\) 没有改变。
  2. 满足 \(z>x\)\(y>w\) 中的其中一个条件。你会发现 \(\Delta\) 恒为负。容易发现不满足条件的那一组都会变号,实际上 \(\Delta=2(z-x)\)(当不满足 \(z>x\)) 或 \(\Delta=2(y-w)\)(当不满足 \(y>w\))。
  3. 不满足 \(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\) 为朋友当且仅当有一条 有向路径 在两者之间。

解法

膜膜 \(\mathtt{OneInDark}\)

如图,\(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]\) 的限制。所以这个做法是不可行的。

posted on 2021-05-31 23:08  Oxide  阅读(110)  评论(0编辑  收藏  举报