XXII Open Cup , Grand Prix of Poland
链接
A. AMPPZ in the times of disease
题解
考虑如果确定了 \(k\) 个集合中的一个点,那么对于其他点,只需要分给距离最近的点即可。容易证明这是唯一合法的构造方案。
考虑如何确定 \(k\) 个集合中的点。容易发现,如果 \(k\neq 1\),那么对于任何一个点,离他最远的点一定不在同一个集合中。同样对于任何一个 \(|S|<k\) 求出离集合 \(S\) 最近点最远的点,那么这个点也一定不在 \(S\) 中。
复杂度 \(O(nk)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=2000010;
struct node{
int x,y;
node(int x=0,int y=0):x(x),y(y){}
}p[N];
ll dis2(node a,node b){return 1ll*(a.x-b.x)*(a.x-b.x)+1ll*(a.y-b.y)*(a.y-b.y);}
int a[N];ll d[N];
int main()
{
int T;scanf("%d",&T);
while(T --> 0)
{
int n,k;scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y);
a[1]=1,d[1]=0;
for(int i=2;i<=n;i++) d[i]=dis2(p[i],p[1]);
for(int i=2;i<=k;i++)
{
int u=0;
for(int j=1;j<=n;j++) if(d[j]>d[u]) u=j;
a[i]=u;
for(int j=1;j<=n;j++) d[j]=min(d[j],dis2(p[j],p[u]));
}
for(int i=1;i<=n;i++)
{
ll dis=1e18;int u=0;
for(int j=1;j<=k;j++) if(dis2(p[i],p[a[j]])<dis) dis=dis2(p[i],p[a[j]]),u=j;
printf("%d ",u);
}
puts("");
for(int i=1;i<=n;i++) d[i]=0;
}
return 0;
}
B. Babushka and her pierogi
咕了
题解
考虑答案的下界,首先交换次数不可能低于 \(n\) 减置换环个数,并且交换极差和不可能低于 \(\frac 12\sum|a_i-p_i|\)。可以证明,存在构造可以达到这个下界。
构造不会。
E. Epidemic
题解
考虑点 \((i,j)\) 表示第 \(i\) 个人在 \(j\) 时间的情况,\((i,j)\) 向 \((i,j-1)\) 连一条有向边,表示如果 \((i,j-1)\) 有可能感染了,那么 \((i,j)\) 也有可能感染了。可以发现,由于应当假设所有不保证是阴性的人都是阳性,所以一个阳性检测除了隔离一个人之外不会导致其他影响。对于一次聚会,把所有 \((x,t)\) 和 \((y,t)\) 之间都连边,表示如果其中一个有可能感染,那么其余也有可能感染。对于一次阴性检测,相当于所有能导致这个点阳性的点都不是阳性,所以它能到达的所有点都是阴性,同时如果一个点不能通过不一定是阴性的点到达某个 \((x,0)\),那么这个点也一定是阴性。
直接处理复杂度是 \(O(n^2)\) 的。考虑实际上只要对每次聚会的人复制点即可,同时注意到一次聚会得到的复制点是等价的,不妨直接用一个点来代替这个团。这样点数和边数都是 \(O(n)\) 的,用 set 维护非阴性且未被隔离的点集。复杂度 \(O(n\log n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<set>
using namespace std;
const int N=2000010;
int deg[N],id[N],neg[N],tt;vector<int>g[N],h[N];
void add(int u,int v){g[u].push_back(v),h[v].push_back(u),++deg[u];}
set<int>res,pos[N];
void new_node(vector<int> tmp)
{
int u=++tt;
for(int v:tmp)
if(!neg[id[v]]) add(u,id[v]);
else res.insert(v);
for(int v:tmp)
pos[u].insert(v),pos[id[v]].erase(v),id[v]=u;
if(!deg[u])
{
for(int v:tmp) res.erase(v);
neg[u]=true;
}
}
void make_neg(int u)
{
if(neg[u]) return;
neg[u]=true;
for(auto v:pos[u]) if(res.count(v)) res.erase(v);
for(int v:g[u]) make_neg(v);
for(int v:h[u]) if(!neg[v] && !--deg[v]) make_neg(v);
}
int las,n;
int decode(){int x;scanf("%d",&x);return (x-1+las)%n+1;}
int main()
{
int t;
scanf("%d",&t);
for(int _=1;_<=t;_++)
{
int m;scanf("%d%d",&n,&m);
tt=n;
for(int i=1;i<=n;i++) res.insert(i),id[i]=i,pos[i].insert(i);
for(int i=1;i<=m;i++)
{
char op[3];scanf("%s",op);
if(op[0]=='K')
{
int k;scanf("%d",&k);
vector<int>tmp;
for(int j=1,x;j<=k;j++) x=decode(),tmp.push_back(x);
new_node(tmp);
}
else if(op[0]=='N')
{
int x=decode();
make_neg(id[x]);
}
else if(op[0]=='P')
{
int x=decode();
if(id[x]) pos[id[x]].erase(x),res.erase(x),id[x]=0;
}
else
{
int x=decode();
if(res.empty()){puts("TAK"),las=0;continue;}
auto y=res.lower_bound(x);
if(y==res.end()) y=res.begin();
printf("NIE %d\n",las=*y);
}
}
las=0;
for(int i=1;i<=tt;i++) id[i]=0,pos[i].clear(),g[i].clear(),h[i].clear(),neg[i]=false,deg[i]=0;
res.clear();
tt=0;
}
return 0;
}
G. Gebyte’s Grind
题解
考虑把每个位置当成一个函数,容易发现每个函数总是可以被表示成一个三元组 \((a,b,c)\),若 \(x\leq a\) 则为 \(0\),否则若 \(x\leq b\) 则为 \(c\),否则为 \(c+(x-b)\)。
容易发现两个这样的折线复合之后也是这样的折线,用线段树维护折线函数,这样每次询问可以直接线段树上二分解决。
复杂度 \(O(q\log n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=2000010;
typedef long long ll;
const ll inf=1e12;
struct node{
ll a,b,c;//if x<=a : 0 . elif x<=b : c . else c+(x-b)
node(ll a=0,ll b=0,ll c=0):a(a),b(b),c(c){}
ll calc(ll x){return x<=a?0:(x<=b?c:c+(x-b));}
};
node operator +(node x,node y)
{
if(x.c<=y.a) return node(y.a-x.c+x.b,y.b-x.c+x.b,y.c);
if(x.c<=y.b) return node(x.a,y.b-x.c+x.b,y.c);
return node(x.a,x.b,x.c-y.b+y.c);
}
node t[N<<2],a[N];
void build(int u,int l,int r)
{
if(l==r){t[u]=a[l];return;}
int mid=(l+r)>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
t[u]=t[u<<1]+t[u<<1|1];
}
void insert(int u,int l,int r,int p,node v)
{
if(l==r){t[u]=v;return;}
int mid=(l+r)>>1;
if(p<=mid) insert(u<<1,l,mid,p,v);
else insert(u<<1|1,mid+1,r,p,v);
t[u]=t[u<<1]+t[u<<1|1];
}
int qry(int u,int l,int r,int p,ll &x)
{
if(l==r)
{
if(t[u].calc(x)==0) return l;
else{x=t[u].calc(x);return l+1;}
}
int mid=(l+r)>>1;
if(p==l)
{
if(t[u<<1].calc(x)==0) return qry(u<<1,l,mid,p,x);
x=t[u<<1].calc(x);
return qry(u<<1|1,mid+1,r,mid+1,x);
}
if(p>mid) return qry(u<<1|1,mid+1,r,p,x);
int v=qry(u<<1,l,mid,p,x);
if(v<=mid) return v;
else return qry(u<<1|1,mid+1,r,mid+1,x);
}
node read()
{
char op[3];ll x;scanf("%s%lld",op,&x);
if(op[0]=='B') return node(x,x,0);
if(op[0]=='K') return node(x-1,inf,x);
return node(0,x,x);
}
int main()
{
int T;
scanf("%d",&T);
while(T --> 0)
{
int n,m;ll X;scanf("%d%d%lld",&n,&m,&X);
for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n);
while(m --> 0)
{
char op[3];int p;scanf("%s%d",op,&p);
if(op[0]=='Z') insert(1,1,n,p,read());
else
{
ll x=X;
int v=qry(1,1,n,p,x);
if(v<=p) puts("-1");
else printf("%d\n",v-1);
}
}
}
return 0;
}
I. Interesting Numbers
题解
和这题做法几乎一致。考虑建 Trie 树,然后设 \(f(x,y)\) 表示当前递归到 \(x\) 的子树和 \(y\) 的子树。对于每个位置,如果当前限制位为 \(1\),那么转移到 \(f(x_0,y_1)+f(x_1,y_0)\),如果限制位为 \(0\) 转移到 \(\max(f(x_0,y_0),f(x_1,y_1))\),再和 \(x,y\) 各自子树大小取 \(\min\)。\(x=y\) 时特判,\(x=0\) 或 \(y=0\) 时特判掉。
复杂度 \(O(n\log a)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=30010,D=20;
int ch[N*D][2],siz[N*D],tt=1;
void insert(int x)
{
int u=1;
for(int i=D-1;i>=0;siz[u]++,u=ch[u][x>>i&1],i--) if(!ch[u][x>>i&1]) ch[u][x>>i&1]=++tt;
siz[u]++;
}
int qry(int a,int b,int x,int d=D-1)
{
if(d==-1) return max(siz[a],siz[b]);
if(!a) return siz[b];if(!b) return siz[a];
if(a==b)
{
if(x>>d&1) return qry(ch[a][0],ch[a][1],x,d-1);
else return max(qry(ch[a][0],ch[a][0],x,d-1),qry(ch[a][1],ch[a][1],x,d-1));
}
if(x>>d&1) return qry(ch[a][0],ch[b][1],x,d-1)+qry(ch[a][1],ch[b][0],x,d-1);
else return max({qry(ch[a][0],ch[b][0],x,d-1),qry(ch[a][1],ch[b][1],x,d-1),siz[a],siz[b]});
}
int main()
{
int t;
scanf("%d",&t);
while(t --> 0)
{
int n,x;scanf("%d%d",&n,&x),++x;
for(int i=1,x;i<=n;i++) scanf("%d",&x),insert(x);
printf("%d\n",qry(1,1,x));
for(int i=1;i<=tt;i++) ch[i][0]=ch[i][1]=siz[i]=0;
tt=1;
}
return 0;
}

浙公网安备 33010602011771号