Warning: session_start(): open(/tmp/sess_40bffe95c03db36849456ed6af424338, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:wangzai_milk:cf2100-2800泛做1 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:wangzai_milk:cf2100-2800泛做1

1322B - Present

$4e5$ 个数,问两两求和后的异或和。

可以按位考虑,考虑第 $k$ 位时给数字模 $2^{k+1}$ ,这样第 $k$ 位为 $1$ 和只可能在 $[2^k,2^{k+1}-1],[2^{k+1}+2^k,2^{k+2}-2]$ 区间,对于每个数lower_bound找位置就好。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=4e5+10;
int a[N],num[N];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	int ans=0;
	for(int k=1;k<=2e7;k<<=1)
	{
		ll tot=0,res=0;
		for(int i=1;i<=n;i++)num[++tot]=a[i]%(k<<1);
		sort(num+1,num+1+tot);
		for(int i=1;i<=tot;i++)
		{
			int l=k-num[i],r=(k<<1)-num[i];
			if(l<=r)res+=lower_bound(num+1,num+i,r)-lower_bound(num+1,num+i,l);
			l=(k<<1)+k-num[i],r=(k<<2)-1-num[i];
			if(l<=r)res+=lower_bound(num+1,num+i,r)-lower_bound(num+1,num+i,l);
		}
		if(res&1)ans|=k;
	}
	printf("%d\n",ans);
	return 0;
}


1322C - Instant Noodles

二分图,右边的点每个有一个点权,对于左边点的集合 $S$ , $N(S)$ 是所有与集合中点邻接的点, $f(S)$ 是 $N(S)$ 的点权之和。问对与所有非空集合 $S$ , $f(S)$ 的 $\gcd$ 。

如果某些右边的点邻接的点完全相同,则缩点,将权值定为它们的和,去除度数零的点,此时所有点权的 $\gcd$ 即答案。

证明:设此时的 $\gcd$ 为 $g$ ,全集 $U$ 。给所有 $c_i$ 除以 $g$ 。若答案为 $k\times g(k > 1)$ ,则 $k\mid f(U_L)$ 。而必定存在 $k\nmid c_j$ (因为 $\gcd$ 已除),取度数最小的这样的 $j$ ,取集合 $S'$ 为左边所有不与 $j$ 相邻的点,则 $N(S')$ 仅不包含 $j$ 与邻接点是 $j$ 的子集的点,所有右边点的点权和被 $k$ 整除,而这些点的点权和不被 $k$ 整除,可以推出 $k\nmid f(S')$ 。矛盾,故不存在这样的 $k$ 。

写法参考了别人的题解,第一次用iota还有vector比较大小什么的,很神秘。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=5e5+10;
ll c[N];
vector<int>g[N],num;
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)vector<int>().swap(g[i]),scanf("%lld",&c[i]);
		for(int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			g[v].pb(u);
		}
		for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
		vector<int>(n).swap(num);
		iota(num.begin(),num.end(),1);
		sort(num.begin(),num.end(),[&](int a,int b){return g[a]<g[b];});
		ll res=0;
		for(int i=0;i<n;i++)
		{
			if(g[num[i]].empty())continue;
			ll f=c[num[i]];
			while(i+1<n&&g[num[i+1]]==g[num[i]])i++,f+=c[num[i]];
			if(!res)res=f;else res=__gcd(res,f);
		}
		printf("%lld\n",res);
	}
	return 0;
}


1322D - Reality Show

$n$ 个观众每个观众有攻击力 $l_i$ ,邀请他花费 $s_i$ ,并直接获得 $c_{l_i}$ 的收益。当现有的两个观众攻击力相同,他们中一个会消失另一个攻击力变 $l_i+1$ 并获得新的收益 $c_{l_i+1}$ (这个过程会不断进行直到选择的观众互不相同)。所选观众的攻击力要求是不上升的,最大化利润。

注意到观众打架消除的过程像二进制加法的进位,处理观众的顺序其实是不影响最终的获利的。由于进位是向高位进的,翻转序列搞一个不下降序列比较容易。我们可以用 $f[l_i][k]$ 表示当前选择的最大攻击力为 $l_i$ ,且 $l_i$ 有 $k$ 个时的最大获利,则 $f[l_i][k]$ 可以向 $f[l_i+1][k/2]$ 转移(因为每次都除二复杂度是可接受的)。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=5005;
const ll inf=(1LL<<60);
int l[N],s[N],c[N];
ll f[N][N],res;
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&l[i]);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	for(int i=1;i<=n+m;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n+m;i++)for(int j=1;j<=n;j++)f[i][j]=-inf;
	for(int i=n;i;i--)
	{
		for(int k=n;k;k--)
		{
			f[l[i]][k]=max(f[l[i]][k],f[l[i]][k-1]+c[l[i]]-s[i]);
			for(int j=l[i],cnt=k;j<n+m&&cnt;j++,cnt>>=1)
			f[j+1][cnt>>1]=max(f[j+1][cnt>>1],f[j][cnt]+(cnt>>1)*c[j+1]);
		}
		for(int j=l[i];j<n+m;j++)f[j+1][0]=max(f[j+1][0],f[j][0]);
	}
	for(int i=1;i<=n+m;i++)res=max(res,f[i][1]);
	printf("%lld\n",res);
	return 0;
}


1325E - Ehab's REAL Number Theory Problem

$n$ 个因子不超过 $7$ 个的数 $a_i$ 。问最少选多少个乘积会是完全平方数。

首先如果某个数字能被完全平方数整除就除掉它,不影响结果。之后由于因子不超过 $7$ 个,所有 $a_i$ 的质因子数不会多于 $2$ 个。如果有 $1$ 就直接选它结束,否则其余数字将会是 $p$ 或 $p·q$ 的形式,前者给 $1,p$ 连边,后者给 $p,q$ 连边,问题就转化成无向图找最小环。找环我们直接bfs,由于每条边意味着两个顶点的数字相乘,最小环中必包含一个不大于 $\sqrt{\max_{a_i}}$ 的点,仅枚举这些点作为起点即可。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=1e6+10;
int pri[N],tot1,a[N],head[N],cnt=-1,num[N],tot2,res=N,d[N],vis[N];
bool jud[N];
vector<int>g[N];
struct Node{int nxt,to;}edges[N];
void addedge(int u,int v){edges[++cnt]=Node{head[u],v},head[u]=cnt;}
void init()
{
	jud[1]=1;
	for(int i=2;i<=1000;i++)
	{
		if(!jud[i])pri[++tot1]=i;
		for(int j=1;j<=tot1&&pri[j]*i<=1000;j++)
		{
			jud[pri[j]*i]=1;
			if(i%pri[j]==0)break;
		}
	}
}
queue<int>q;
void bfs(int s)
{
	memset(d,0x3f,sizeof(d));
	memset(vis,0,sizeof(vis));
	queue<int>().swap(q);
	q.push(s),d[s]=0;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=head[u];~i;i=edges[i].nxt)
		{
			int v=edges[i].to;
			if(vis[i])continue;
			if(d[v]==0x3f3f3f3f)d[v]=d[u]+1,vis[i]=vis[i^1]=1,q.push(v);
			else {res=min(res,d[u]+d[v]+1);}
		}
	}
}
int main()
{
	int n;
	memset(head,-1,sizeof(head));
	scanf("%d",&n),init();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		for(int j=1;j<=tot1;j++)
		{
			while(a[i]%(pri[j]*pri[j])==0)a[i]/=pri[j]*pri[j];
			if(a[i]%pri[j]==0)g[i].pb(pri[j]),a[i]/=pri[j],num[++tot2]=pri[j];
		}
		if(a[i]!=1)g[i].pb(a[i]),num[++tot2]=a[i];
		if(g[i].empty()){puts("1");return 0;}
		if(g[i].size()<2)g[i].pb(1),num[++tot2]=1;
	}
	sort(num+1,num+1+tot2);
	tot2=unique(num+1,num+1+tot2)-num-1;
	for(int i=1;i<=n;i++)
	{
		g[i][0]=lower_bound(num+1,num+1+tot2,g[i][0])-num;
		g[i][1]=lower_bound(num+1,num+1+tot2,g[i][1])-num;
		addedge(g[i][0],g[i][1]),addedge(g[i][1],g[i][0]);
	}
	for(int i=1;i<=tot2;i++)if(num[i]<=1000)bfs(i);
	printf("%d\n",res==N?-1:res);
	return 0;
}


1325F - Ehab's Last Theorem

给无重边自环的 $n$ 个节点的无向图,要求构造至少 $\lceil\sqrt{n}\rceil$ 个节点的简单环,或 $\lceil\sqrt{n}\rceil$ 个节点的独立集。

dfs树上每个非树边 $(u,v)$ 都代表了一个大小为 $|\mathrm{dep}_u-\mathrm{dep}_v|+1$ 的环(dfs树的一个性质是所有非树边连接一个点与它的某个祖先)。搞一个栈记录一下节点,如果环的大小满足 $\lceil\sqrt{n}\rceil$ ,选择方案2直接输出。

而如果不存在 $|\mathrm{dep}_u-\mathrm{dep}_v|+1\ge\lceil\sqrt{n}\rceil$ ,则鸽巢原理说明每个点最多连出 $\lceil\sqrt{n}\rceil-2$ 条非树边,最多限制 $\lceil\sqrt{n}\rceil-1$ 个点不可取,所以必能得到 $\lceil\sqrt{n}\rceil$ 大小独立集。从dfs树的叶子节点往上取即可。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=2e5+10;
queue<int>q;
int head[N],cnt,st[N],top,vis[N],d[N],f[N],bloc,done=0,banned[N];
struct Node{int nxt,to;}edges[N*2];
void addedge(int u,int v){edges[++cnt]=Node{head[u],v},head[u]=cnt;}
void dfs1(int u)
{
	vis[u]=1,st[++top]=u;
	int isleaf=1;
	for(int i=head[u];~i&&!done;i=edges[i].nxt)
	{
		int v=edges[i].to;
		if(f[u]==v)continue;
		if(!vis[v])isleaf=0,f[v]=u,d[v]=d[u]+1,dfs1(v);
		else if(d[u]-d[v]+1>=bloc)
		{
			printf("2\n%d\n",d[u]-d[v]+1);
			for(int j=d[v];j<=d[u];j++)printf("%d ",st[j]);
			puts(""),done=1;
		}
	}
	if(isleaf)q.push(u);
	top--;
}
int main()
{
	int n,m;
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m),bloc=ceil(sqrt(n))+0.1;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v),addedge(v,u);
	}
	d[1]=1,dfs1(1);
	if(!done)
	{
		printf("1\n");
		while(!q.empty()&&bloc)
		{
			int u=q.front();q.pop();
			if(!banned[u])
			{
				printf("%d ",u),bloc--,banned[u]=1;
				for(int i=head[u];~i;i=edges[i].nxt)
				{
					int v=edges[i].to;
					banned[v]=1;
				}
			}
			if(f[u])q.push(f[u]);
		}
		assert(bloc==0);
		puts("");
	}
	return 0;
}


1326E - Bombs

给出长度为 $n$ 的两个排列 $p,q$ ,按照顺序从 $1$ 到 $n$ ,把 $p_i$ 加入集合,如果位置 $i$ 有炸弹则从集合中取出一个最大值,结果是最后集合中的最大值。第 $i$ 个答案回答的是 $q_1,q_2,\ldots q_{i-1}$ 处有炸弹时的结果。

一个比较厉害的转换思维。我们观察到答案是单调不上升的,如果答案至多为 $x$ ,我们就需要让 $x+1,x+2,\ldots,n$ 都被炸掉,条件就是对于每个位置右边大于 $x$ 的 $p_i$ 的数量都不多于右边的炸弹数量。可以线段树维护 $右面不小于当前答案的p_i的数量-右面炸弹数量$ ,如果小于等于 $ 0 $ 则减小当前答案。

点击以显示 ⇲

点击以隐藏 ⇱

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N=3e5+10;
int p[N],q[N],maxn[N<<2],lz[N<<2],pos[N];
void pushdown(int idx)
{
	int t=lz[idx];
	lz[idx]=0,lz[idx<<1]+=t,lz[idx<<1|1]+=t;
	maxn[idx<<1]+=t,maxn[idx<<1|1]+=t;
}
void add(int idx,int l,int r,int a,int b,int x)
{
	if(a<=l&&b>=r){lz[idx]+=x,maxn[idx]+=x;return;}
	int mid=(l+r)>>1;
	pushdown(idx);
	if(b<=mid)add(idx<<1,l,mid,a,b,x);
	else if(a>mid)add(idx<<1|1,mid+1,r,a,b,x);
	else add(idx<<1,l,mid,a,b,x),add(idx<<1|1,mid+1,r,a,b,x);
	maxn[idx]=max(maxn[idx<<1],maxn[idx<<1|1]);
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&p[i]),pos[p[i]]=i;
	for(int i=1;i<=n;i++)scanf("%d",&q[i]);
	int res=n;
	add(1,1,n,1,pos[n],1);
	for(int i=1;i<=n;i++)
	{
		printf("%d ",res);
		if(i==n)break;
		add(1,1,n,1,q[i],-1);
		while(maxn[1]<=0)--res,add(1,1,n,1,pos[res],1);
	}
	puts("");
	return 0;
}


2020-2021/teams/wangzai_milk/cf2100-2800泛做1.1596386330.txt.gz · 最后更改: 2020/08/03 00:38 由 zars19