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();
}