• [2021.5] 我要交作业!


    ( ext{Encounter and Farewell})

    题目

    传送门

    解法

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

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

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

    ( ext{CodeForces - 1503D Flip the Cards})

    题目

    传送门

    解法

    攷,想了好久才懂。

    证明 (a_i,b_ile n) 无解。

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

    [min_{jle i}f[j]>max_{j>i} f[j] ]

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

    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) 中的任何一个条件。这种情况是不存在的。

    证毕。

    代码

    戳这,具体实现有些小技巧。

    ( 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) 为朋友当且仅当有一条 有向路径 在两者之间。

    解法

    膜膜 (mathtt{OneInDark})

    如图,(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}) 的概率被我赶上了两次… 是不是哪里出了点问题?

  • 相关阅读:
    hdu3746 KMP的next数组应用,求项链首尾项链循环
    hdu4067 费用流(混合欧拉的宽展和延伸)
    hdu4067 费用流(混合欧拉的宽展和延伸)
    hdu1501 记忆化搜索
    hdu1501 记忆化搜索
    hdu1316 大数
    hdu1316 大数
    hdu4411 经典费用里建图
    hdu4411 经典费用里建图
    hdu4768 非常规的二分
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/14726687.html
Copyright © 2020-2023  润新知