zoukankan      html  css  js  c++  java
  • Codeforces Global Round 17

    \(\tt noip\) 之后的第一场线上赛,感觉手感退化了很多啊,不知道上红的目标能不能如期实现呢?

    D. Not Quite Lee

    题目描述

    数轴上有 \(n\) 个窗口,第 \(i\) 个窗口的长度为 \(b_i\)(包含这么多连续的整数),定义一个窗口的权值为包含数字的和,问有多少个窗口的子序列满足存在一种滑动方案使得权值和为 \(0\)

    \(n\leq 2\cdot 10^5\)

    解法

    考虑调整法,一开始可以取总和为 \(\sum \frac{c_i(c_i-1)}{2}\) 的窗口组合,那么滑动相当于把权值 \(+c_i\) 或者 \(-c_i\),那么合法的充要条件是存在序列 \(\{x_i\}\) 满足 \(\sum c_i\cdot x_i=\sum\frac{c_i(c_i-1)}{2}\)

    根据裴蜀定理可以转化为 \(\sum\frac{c_i(c_i-1)}{2}|\gcd(c_1,c_2...c_n)\),但是好像还是不可做。

    我们观察 \(\sum\frac{c_i(c_i-1)}{2}\) 有什么性质,其中特殊的是 \(2\) 这个常数,这提示我们可以着重讨论奇偶性。

    然后观察到如果原序列中存在奇数那么一定合法,因为此时一定满足 \(\sum \frac{c_i(c_i-1)}{2}|\gcd\),尝试扩展这个观察,也就是把每个子序列在满足 \(\gcd|2^l\) 的最大的 \(l\) 处统计。

    对于 \(l>0\),我们把限制拆成 \(\sum\frac{c_i(c_i-1)}{2}|2^l\and \sum\frac{c_i(c_i-1)}{2}|\frac{g}{2^l}\),也就是满足 \(c_i|2^l\and c_i\not| 2^{l+1}\) 的有偶数个并且至少有 \(1\) 个,所以可以用容斥原理简单计算。

    #include <cstdio>
    const int M = 200005;
    const int MOD = 1e9+7;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,a[M],pw[M];
    signed main()
    {
    	n=read();pw[0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		int x=read(),cnt=0;
    		while(x%2==0) cnt++,x/=2;
    		a[cnt]++;
    		pw[i]=pw[i-1]*2%MOD;
    	}
    	int ans=pw[n]-pw[n-a[0]],y=n-a[0];
    	for(int i=1;i<=30;i++)
    	{
    		int x=y;y-=a[i];
    		if(x-1<=y) continue;
    		ans+=pw[x-1]-pw[y];
    	}
    	printf("%lld\n",(ans%MOD+MOD)%MOD);
    }
    

    E. AmShZ and G.O.A.T.

    题目描述

    定义一个序列为坏当且仅当严格大于平均数的数量 大于 严格小于平均数的数量。

    问最少删除多少个元素使得最后的序列的任何子序列都不为坏。

    \(n\leq 2\cdot 10^5\),保证原序列单增。

    解法

    考虑转化判据,原序列的任何子序列不为坏当且仅当原序列的任何一个长度为 \(3\) 的子序列不为坏,必要性显然,下证充分性:

    考虑对于如果 \(a_{i+1}-a_i<a_i-a_1\),那么 \(\{1,i,i+1\}\) 就是一组坏的序列。否则我们可以知道对于任何一个子序列都有 \(c_{\lceil\frac{k}{2}\rceil}\leq AVG\)(因为是凸函数,中间位置一定在下方),这足以说明不存在坏的子序列。

    就上面这个简单的证明我证了一周,当然是边学文化课有空余时间再证的

    那么我们只需要保证 \(a_{i+1}-a_i\geq a_i-a_1\) 就可以得到好的序列,考虑一个一个加数,那么新数与首项的距离每次一定翻倍,所以在非常数序列的情况下,序列长度是 \(O(\log a)\) 的。

    所以我们枚举首项,然后贪心地找最近合法的下一项,注意特判相等的情况,那么时间复杂度 \(O(n\log n\log a)\)

    总结

    本题的难点其实是结论,这种类型就是判断最基本的情况就有了充分性。

    证明方法难以用语言表达,自己体会一下吧

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,ans,a[M];
    signed main()
    {
    	T=read();
    	while(T--)
    	{
    		n=read();ans=0;
    		for(int i=1;i<=n;i++)
    			a[i]=read();
    		for(int i=1;i<=n;i++)
    		{
    			if(a[i]==a[i-1]) continue;
    			int j=i,res=1;
    			while(j<=n)
    			{
    				j=lower_bound(a+j+1,
    				a+n+1,2*a[j]-a[i])-a;
    				if(j<=n) res++;
    			}
    			ans=max(ans,res);
    		}
    		printf("%d\n",n-ans);
    	}
    }
    

    G. AmShZ Wins a Bet

    题目描述

    点此看题

    解法

    虽然评分虚高但还是做不来,但是补这种题还是多有意思的‍

    经过我的尝试发现贪心是不行的,有一个关键的 \(\tt observation\):如果删除一对 (),那么其中的字符必须要全部删除,这是因为只需要使用相邻 () 的删除操作就可以得到最优解。

    这说明我们可以把问题转化成保留原序列的若干连续段,使得剩下的串字典序最小

    显然这是一个简单的线性 \(dp\) 模型,考虑到字典序的特性我们从后往前 \(dp\),设 \(f_i\) 表示操作后 \(i\) 个字符留下来的字典序最小的串,可以在 \(f_{i+1}\) 的基础上直接添加,还可以找到和当前的 ( 在原串上配对 ) 的位置,然后删除这一整段(根据结论这是唯一需要考虑的),所以可以得到(设 \(nxt_i\) 表示配对字符的位置):

    \[f_i=\min(s_i+f_{i+1},f_{nxt_{i+1}}) \]

    问题变成了快速比较两个字符串的字典序,肯定首选哈希求出最长公共前缀,可以主席树暴力维护,更好的方法是维护一个动态增加叶子的 \(\tt trie\) 树,用树上倍增的方法跳最长公共前缀(如果哈希值相同就往上跳)

    时间复杂度 \(O(n\log n)\),注意 \(\tt trie\) 树上尽量不要重复开节点要不然容易乱套。

    总结

    字典序问题贪心不是唯一解,倒序 \(dp\) 同样充分利用了字典序的性质。

    把复杂的问题转化成简单的 \(dp\) 模型,本题就是先证明只需要使用连续段就可以转线性 \(dp\)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 300005;
    #define ull unsigned long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,cnt,q[M],f[M],ch[M][2],fa[M][20],nxt[M];
    ull dp[M][20],pw[M]={1};char s[M];
    void ins(int p,int c)
    {
    	if(ch[p][c]) return ;
    	int x=++cnt;ch[p][c]=x;
    	fa[x][0]=p;dp[x][0]=c;
    	for(int i=1;i<=19;i++)
    	{
    		int to=fa[x][i-1];
    		fa[x][i]=fa[to][i-1];
    		dp[x][i]=pw[1<<i-1]*dp[to][i-1]+dp[x][i-1];
    	}
    }
    int cmp(int x,int y)//string x < string y is ture ?
    {
    	for(int i=19;i>=0;i--)
    		if(fa[x][i] && fa[y][i] && dp[x][i]==dp[y][i])
    			x=fa[x][i],y=fa[y][i];
    	if(x==1) return 1;
    	if(y==1) return 0;
    	return dp[x][0]<dp[y][0];
    }
    signed main()
    {
    	scanf("%s",s+1),n=strlen(s+1);
    	for(int i=1;i<=n;i++)
    		pw[i]=pw[i-1]*371;
    	cnt=f[n+1]=1;
    	for(int i=n;i>=1;i--)
    	{
    		if(s[i]=='(' && m) nxt[i]=q[m--];
    		if(s[i]==')') q[++m]=i;
    		if(s[i]==')')//add it dirctly
    		{
    			ins(f[i+1],1);f[i]=ch[f[i+1]][1];
    			continue;
    		}
    		ins(f[i+1],0);int to=ch[f[i+1]][0];
    		if(!nxt[i] || cmp(to,f[nxt[i]+1])) f[i]=to;
    		else f[i]=f[nxt[i]+1];
    	}
    	int nw=f[1];
    	while(nw!=1)
    	{
    		if(dp[nw][0]==0) printf("(");
    		else printf(")");
    		nw=fa[nw][0];
    	}
    	puts("");
    }
    

    H.Squid Game

    题目描述

    点此看题

    解法

    事实证明如果调不出来一定要拍,而且要用强力的 datamaker 来拍。

    首先考虑对于最优选点方案,我们以被选取点的一个点为根建树,我们可以在脑海中想象枚举根的过程,但在下面的讨论中我们不妨以 \(1\) 为根建树。

    那么在选取了 \(1\) 之后我们发现所有 祖先-后代 类型的要求(要求一)是没有被满足的,但是 \(\tt lca\) 不是端点的要求(要求二)是都被满足了的,所有我们只需要解决要求一。

    到这一步我们可以直接使用延迟贪心,也就是我们做 \(\tt dfs\),维护子树内未完成的要求。如果有一种要求不能上传给父亲,那么证明必须选取这个点,注意从 \(v_1\) 的要求上传到 \(u\),如果 \(v_2\) 子树内有点被选取,那么 \(v_1\) 上传的要求是不需要考虑的,用 \(\tt set\) 维护可以做到 \(O(n\log^2 n)\)

    但是如果我们暴力枚举根复杂度爆炸,因为根只带来了 \(1\) 的影响,我们尝试用讨论的方法解决它。如果此时要求二已经全部被解决了,那么我们就不需要选取 \(1\),此时答案一定最优;如果此时要求二没有全部被满足,我们证明此时选取 \(1\) 是必要的,因为我们的延迟贪心是在最浅的位置放置,那么没被满足的要求二的 \(\tt lca\) 一定在更浅的地方,所以说单独处理是必要的。

    总结

    路径问题可以考虑定根,根的作用有:作为路径的起点;解决掉若干情况。

    较小的影响可以通过讨论法解决,可以通过证明必要性来说明操作的最优性。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <set>
    using namespace std;
    const int M = 300005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,tot,ans,f[M],a[M],b[M],c[M],d[M],fa[M][20];
    set<int> s[M];vector<int> o[M];
    struct edge
    {
    	int v,next;
    }e[M];
    void dfs0(int u,int p)
    {
    	d[u]=d[p]+1;fa[u][0]=p;
    	for(int i=1;i<=19;i++)
    		fa[u][i]=fa[fa[u][i-1]][i-1];
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==p) continue;
    		dfs0(v,u);
    	}
    }
    int lca(int u,int v)
    {
    	if(d[u]<d[v]) swap(u,v);
    	for(int i=19;i>=0;i--)
    		if(d[fa[u][i]]>=d[v])
    			u=fa[u][i];
    	if(u==v) return u;
    	for(int i=19;i>=0;i--)
    		if(fa[u][i]^fa[v][i])
    			u=fa[u][i],v=fa[v][i];
    	return fa[u][0];
    }
    void dfs1(int u,int p)
    {
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==p) continue;
    		dfs1(v,u);
    		c[u]+=c[v];
    	}
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==p) continue;
    		if(c[u]-c[v]) continue;
    		if(s[u].size()<s[v].size())
    			swap(s[u],s[v]);
    		for(auto x:s[v]) s[u].insert(x);
    	}
    	if(s[u].size() && *s[u].rbegin()==d[u]-1)
    		c[u]++,ans++,s[u].clear();
    	for(auto x:o[u]) s[u].insert(x);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=2;i<=n;i++)
    	{
    		int j=read();
    		e[++tot]=edge{i,f[j]},f[j]=tot;
    	}
    	dfs0(1,0);
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read(),x=lca(u,v);
    		if(u==fa[v][0] || v==fa[u][0])
    		{
    			puts("-1");
    			return 0;
    		}
    		if(x==v) o[u].push_back(d[v]);
    		else if(x==u) o[v].push_back(d[u]);
    		else a[++k]=u,b[k]=v;
    	}
    	dfs1(1,0);
    	for(int i=1;i<=k;i++)
    		if(ans-c[a[i]]-c[b[i]]==0)
    		{
    			ans++;
    			break;
    		}
    	printf("%d\n",ans);
    }
    

    I. Mashtali vs AtCoder

    题目描述

    给定一棵 \(n\) 个点的数,然后分别固定 \(1,2...k\) 来做 \(n\) 次游戏。每次游戏的规则是:删除一条边,如果此时存在一个连通块内不包含固定点,那么直接删去这个连通块,不能操作者败。

    你需要对这 \(n\) 次游戏分别求出是先手必胜还是先手必败。

    \(n\leq 3\cdot 10^5\)

    解法

    首先我们考虑 \(k=1\) 的情况,它就是这道题的弱化版 Game on tree

    通过打表发现子树 \(u\)\(SG\) 值等于所有儿子 \(v\)\(SG+1\) 的异或和,证明:

    如果 \(u\)\(k\) 个儿子,那么我们把 \(u\) 复制 \(k\) 份,那么我们得到了根节点只有一个儿子的独立子游戏,根的 \(SG\) 就等于这些独立子游戏的 \(SG\) 异或和,可以归纳证明根节点只有一个儿子的游戏的 \(SG\) 为儿子的 \(SG+1\)

    对于两个节点的情况显然成立,如果我们删除根节点的边那么 \(SG=0\),否则归纳可知转移到的所有子状态的 \(SG\) 都是原来的 \(SG+1\),把这个过程放在 \(\tt mex\) 上考虑你就发现根的 \(SG\) 值是原来的 \(SG+1\)


    对于 \(k>1\) 的情况,游戏的 \(SG\) 为:把前 \(k\) 个节点的虚树缩成一个点,得到根的 \(SG\) 值异或上虚树边数的奇偶性。证明我还没懂,如果读者会请不吝赐教。

    那么我们以 \(1\) 为根建树,添加一个点就把到根路径上的所有边加入即可,时间复杂度 \(O(n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 300005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,tot,f[M],p[M],dp[M],vis[M];
    struct edge
    {
    	int v,next;
    }e[2*M];
    void dfs(int u,int fa)
    {
    	p[u]=fa;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa) continue;
    		dfs(v,u);
    		dp[u]^=dp[v]+1;
    	}
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		e[++tot]=edge{v,f[u]},f[u]=tot;
    		e[++tot]=edge{u,f[v]},f[v]=tot;
    	}
    	dfs(1,0);
    	int cur=dp[1];
    	printf("%d",(cur)?1:2);
    	for(int i=2;i<=n;i++)
    	{
    		vector<int> d;
    		for(int x=i;p[x] && !vis[x];x=p[x])
    			vis[x]=1,d.push_back(x);
    		for(auto x:d)
    		{
    			cur^=dp[x]+1;
    			cur^=dp[x];
    			cur^=1;
    		}
    		printf("%d",(cur)?1:2);
    	}
    	puts("");
    }
    
  • 相关阅读:
    卷积层中的特征冗余
    【跨模态智能分析】人物关系检测、指代表达、指代分割
    【第1周作业】“乘风破浪的程序员小哥哥小姐姐” 成团时刻
    2020年秋季《软件工程》开课啦
    初入科研领域,如何正确做科研
    【WACV2020】ULSAM: Ultra-Lightweight Subspace Attention Module
    【ECCV2020】 Context-Gated Convolution
    【ECCV2020】WeightNet: Revisiting the Design Space of Weight Networks
    【ECCV2020】Image Inpainting via a Mutual Encoder-Decoder with Feature Equalizations
    【新生学习】课程学习记录
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15645050.html
Copyright © 2011-2022 走看看