AtCoder Regular Contest 122

C.Calculator

题目描述

两个变量 \(x,y\),初始时 \(x=y=0\),可以把:\(x\)\(1/y\)\(y\)\(1/x\),在 \(130\) 步之内把 \(x\) 变成 \(n\)

\(n\leq 10^{18}\)

解法

这道题和二进制没什么关系啊,观察一下发现 \(3,4\) 总是交替操作,就是类似斐波那契的东西了。

可以把 \(n\) 做斐波那契拆分,大的框架是 \(3,4\) 交替操作,在适当时机加 \(1\) 即可。

#include <cstdio>
#include <vector>
using namespace std;
const int M = 100005;
#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,m,f[M];vector<int> ans;
signed main()
{
	n=read();
	if(n==1)
	{
		printf("1\n1\n");
		return 0;
	}
	f[0]=1;f[1]=2;
	for(m=2;;m++)
	{
		f[m]=f[m-1]+f[m-2];
		if(f[m]>n) {m--;break;}
	}
	int ls=m,op=0;n-=f[m];
	for(int i=m-1;i>=0;i--)
		if(n>=f[i])
		{
			ans.push_back(1+op);
			for(int j=ls;j>i;j--)
				op^=1,ans.push_back(3+op);
			n-=f[i];ls=i;
		}
	ans.push_back(1+op);
	for(int j=ls;j>=0;j--)
		op^=1,ans.push_back(3+op);
	printf("%lld\n",ans.size());
	if(op==1)
	{
		for(int i=0;i<ans.size();i++)
			printf("%lld\n",((ans[i]-1)^1)+1);
	}
	else
	{
		for(int i=0;i<ans.size();i++)
			printf("%lld\n",ans[i]);
	}
}

D.XOR Game

题目描述

\(2n\) 个数,第 \(i\) 个数是 \(a_i\),两个人轮流选数,每一轮选出的数的异或值记在本子上,最后本子上数的最大值就是得分,第一个人想最大化得分,第二个人想最小化得分,两个都最优操作,问最后结果。

\(1\leq n\leq 2\cdot 10^5,0\leq a_i<2^{30}\)

解法

二进制问题就从高位到低位贪心考虑,设现在考虑第 \(k\) 位。

把包含第 \(k\) 位的数和不含第 \(k\) 位的数分开,如果包含第 \(k\) 位的数有奇数个,那么得分一定有 \(k\) 这一位,如果包含第 \(k\) 位的数有偶数个,那么得分一定没有 \(k\) 这一位。

第一种情况,设某个包含第 \(k\) 位的数为 \(x\),第二个人会从另一个集合中找和 \(x\) 异或的最小值,记为 \(val\),对于所有的 \(x\) 计算 \(val\),取最小的就是答案,可以用 \(\tt tire\) 树解决。

第二种情况,两个集合一定只会内部匹配,所以分成两个子问题递归下去即可。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 400005;
const int N = 32*M;
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,rt,a[M],b[M],ls[N],rs[N];
void init()
{
	for(int i=0;i<=cnt;i++) ls[i]=rs[i]=0;
	m=cnt=rt=0;
}
void add(int &x,int y,int w)
{
	if(!x) x=++cnt;
	if(w==-1) return ;
	if(y&(1<<w)) add(rs[x],y,w-1);
	else add(ls[x],y,w-1);
}
int ask(int x,int y,int w)
{
	if(w==-1) return 0;
	if(y&(1<<w))
	{
		if(rs[x]) return ask(rs[x],y,w-1);
		return ask(ls[x],y,w-1)+(1<<w);
	}
	if(ls[x]) return ask(ls[x],y,w-1);
	return ask(rs[x],y,w-1)+(1<<w);
}
int zxy(int l,int r,int w)
{
	if(l>r || w==-1) return 0;
	int mid=r;
	for(int i=l;i<=r;i++)
		if(a[i]&(1<<w))
		{
			mid=i-1;
			break;
		}
	if((r-mid)&1)
	{
		init();
		for(int i=l;i<=mid;i++)
			add(rt,a[i],30);
		for(int i=mid+1;i<=r;i++)
			b[++m]=ask(rt,a[i],30);
		sort(b+1,b+1+m);
		return b[1];
	}
	return max(zxy(l,mid,w-1),zxy(mid+1,r,w-1));
}
signed main()
{
	n=2*read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	sort(a+1,a+1+n);
	printf("%d\n",zxy(1,n,30));
}

E.Increasing LCMs

题目描述

给定长度为 \(n\) 的数列 \(a\),要求重新安排顺序使得前缀 \(\tt lcm\) 单调递增。

\(n\leq 100,a_i\leq 10^{18}\)

解法

直接排是做不动的,因为现在放数字会影响后面。那从后面往前面放行不行,首先无论前面什么顺序,\(\tt lcm\) 是定值,如果当前合法那么只要前面的子问题合法即可,唯一的问题是如果有多个可以放的数选哪个放进去?

你发现如果一个数可以放那么它一定有前面没有的质因子的幂次,我们称之为数的独特性。在我们放完进入前面的子问题后原来独特的数还是独特,而且独特数只会越来越多,所以现在放谁是没有影响。

现在的问题是找到任意一个合法的数放进去即可,设现在判断的数是 \(x\),那么判断方法是 \(\gcd(lcm(a_i),x)<x\),但是会爆整数,考虑 \(lcm\) 实际上是质因子幂次取 \(\max\)\(\gcd\) 实际上是质因子次数取 \(\min\),所以可以改写成 \(lcm(\gcd(a_i,x))<x\),这个东西是不会超过 \(x\) 的,所以就不会爆了,时间复杂度 \(O(n^3\log a)\)

总结一下,这类构造问题可以向后效性小的方面考虑,这道题从后往前后效性更小。

#include <cstdio>
const int M = 105;
#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],b[M],use[M];
int gcd(int a,int b)
{
	return !b?a:gcd(b,a%b);
}
int lcm(int a,int b)
{
	return a/gcd(a,b)*b;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=n;i>=1;i--)
	{
		for(int j=1;j<=n;j++)
		{
			if(use[j]) continue;
			int d=1;
			for(int k=1;k<=n;k++)
				if(!use[k] && j!=k)
					d=lcm(d,gcd(a[j],a[k]));
			if(d<a[j]) {use[j]=1;b[i]=a[j];break;}
		}
		if(!b[i]) {puts("No");return 0;}
	}
	puts("Yes");
	for(int i=1;i<=n;i++)
		printf("%lld ",b[i]);
}

F.Domination

题目描述

\(n\) 个红色的石子,有 \(m\) 个蓝色的石子,你可以任意移动蓝色的石子,代价是曼哈顿距离。

用最小的代价,使得每个红色石子的右上方都有至少 \(k\) 个蓝色石子,石子的位置可以重叠,右上方是闭区间。

\(1\leq k\leq 10,1\leq n,m\leq 10^5\)

解法

应该往图论想这个问题,首先考虑 \(k=1\) 的时候单个红色石头应该怎么建图,这是个二维问题,要把它分解成一维的才能建到图上去,限制的主体是红色石子,把蓝色石子当成解决问题的方式。根据上面原则我们这样建图:

  • 红色石子在 \(Y\) 轴方向移动,往下走一步消耗 \(1\),往上走一步消耗 \(0\),要从 \(RY_i\) 移动到 \(BY_j\)
  • 蓝色石子在 \(X\) 轴方向移动,往右走一步消耗 \(1\),往左走一步消耗 \(0\),要从 \(BX_j\) 移动到 \(RX_i\)
  • 要把这两个过程串联起来,连接 \((0,BY_j)\)\((BX_j,0)\),边权为 \(0\)

那么我们要求的最短路是 \((0,RY_i)\)\((RX_i,0)\)

我们考虑扩展到 \(k=1\) 但是有多个红色石头的情况,发现是一个蓝色石子管辖多个红色石子,首先把红色石子按 \(x\) 从小到大排序,那么我们只需要考虑 \(y\) 从大到小递减的这些点,假如一个蓝色石子管辖区间 \([l,r]\),相当于处理 \((RX_r,RY_l)\) 这个红色石子。然后我们要把若干个区间串联起来,用 \(0\) 代价边即可,所以这样建图:

  • 连接 \((RX_i,0)\)\((0,RY_{i+1})\),边权为 \(0\)

那么我们要求的最短路是 \((0,RY_1)\)\((RX_n,0)\)

现在考虑扩展到 \(k\leq 10\) 的情况,其实就是选 \(k\) 条最短路,但是蓝色石子的边只能用一次,所以把蓝色石子的边建成流量为 \(1\) 的边,然后跑费用流即可,时间复杂度 \(O(k\cdot n\log n)\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
const int M = 400005;
const int inf = 0x3f3f3f3f;
#define pii pair<int,int>
#define make make_pair
#define ll 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,k,p,S,T,lx,ly,tx[M],ty[M];
int tot,f[M],flow[M],lst[M],pre[M];ll dis[M];
struct node
{
	int x,y;
	bool operator < (const node &b) const
	{
		if(x==b.x) return y<b.y;
		return x<b.x;
	}
}a[M],b[M],c[M];
struct edge
{
	int v,c,f,next;
}e[10*M];
struct zxy
{
	int u;ll c;
	bool operator < (const zxy &b) const
	{
		return c>b.c;
	}
};
void add(int u,int v,int c,int fl)
{
	e[++tot]=edge{v,c,fl,f[u]},f[u]=tot;
	e[++tot]=edge{u,-c,0,f[v]},f[v]=tot;
}
int spfa()
{
	priority_queue<zxy> q;
	memset(dis,0x3f,sizeof dis);
	dis[S]=0;flow[S]=inf;q.push(zxy{S,0});
	while(!q.empty())
	{
		int u=q.top().u,w=q.top().c;q.pop();
		if(w>dis[u]) continue;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;
			if(e[i].f>0 && dis[v]>dis[u]+e[i].c)
			{
				dis[v]=dis[u]+e[i].c;
				flow[v]=min(flow[u],e[i].f);
				lst[v]=i;pre[v]=u;
				q.push(zxy{v,dis[v]});
			}
		}
	}
	return dis[T]<0x3f3f3f3f3f3f3f3f;
}
void solve()
{
	ll cost=0;
	while(spfa())
	{
		cost+=dis[T]*flow[T];
		int zy=T;
		while(zy!=S)
		{
			e[lst[zy]].f-=flow[T];
			e[lst[zy]^1].f+=flow[T];
			zy=pre[zy];
		}
	}
	printf("%lld\n",cost);
}
signed main()
{
	n=read();m=read();k=read();tot=1;
	for(int i=1;i<=n;i++)
	{
		a[i].x=read(),a[i].y=read();
		tx[++lx]=a[i].x;
		ty[++ly]=a[i].y;
	}
	for(int i=1;i<=m;i++)
	{
		b[i].x=read();b[i].y=read();
		tx[++lx]=b[i].x;
		ty[++ly]=b[i].y;
	}
	sort(tx+1,tx+1+lx);
	sort(ty+1,ty+1+ly);
	lx=unique(tx+1,tx+1+lx)-tx-1;
	ly=unique(ty+1,ty+1+ly)-ty-1;
	for(int i=1;i<lx;i++)
	{
		add(i,i+1,tx[i+1]-tx[i],inf);
		add(i+1,i,0,inf);
	}
	for(int i=1;i<ly;i++)
	{
		add(i+lx+1,i+lx,ty[i+1]-ty[i],inf);
		add(i+lx,i+lx+1,0,inf);
	}
	for(int i=1;i<=m;i++)
	{
		int u=lower_bound(tx+1,tx+lx+1,b[i].x)-tx;
		int v=lower_bound(ty+1,ty+ly+1,b[i].y)-ty+lx;
		add(v,u,0,1);
	}
	sort(a+1,a+1+n);
	for(int i=n;i>=1;i--)
		if(c[p].y<a[i].y || !p) c[++p]=a[i];
	reverse(c+1,c+1+p);
	for(int i=1;i<p;i++)
	{
		int u=lower_bound(tx+1,tx+lx+1,c[i].x)-tx;
		int v=lower_bound(ty+1,ty+ly+1,c[i+1].y)-ty+lx;
		add(u,v,0,inf); 
	}
	S=0;T=lx+ly+1;
	int u=lower_bound(tx+1,tx+lx+1,c[p].x)-tx;
	int v=lower_bound(ty+1,ty+ly+1,c[1].y)-ty+lx;
	add(S,v,0,k);
	add(u,T,0,k);
	solve();
}
posted @ 2021-06-14 20:47  C202044zxy  阅读(121)  评论(0编辑  收藏  举报