2020 Petrozavodsk Winter Camp, Jagiellonian U Contest
G. Invited Speakers
给出 张桌子和 个人的座标,将其一一配对,配对可以用一些线段连接,线段不能交叉。求方案。
所有点横纵座标都不一样。
设一个大常数 ,先将桌子和人分别按照横座标排序,然后这样构造:
即
Code:
#include<bits/stdc++.h>
#define maxn 15
using namespace std;
int n,C;
pair<int,int>a[maxn],b[maxn];
#define x first
#define y second
int main()
{
int T;
for(scanf("%d",&T);T--;){
scanf("%d",&n),C=0;
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y),C=max(C,max(abs(a[i].x),abs(a[i].y)));
for(int i=1;i<=n;i++) scanf("%d%d",&b[i].x,&b[i].y),C=max(C,max(abs(b[i].x),abs(b[i].y)));
sort(a+1,a+1+n),sort(b+1,b+1+n);
for(int i=1;i<=n;i++){
int X=C+n-i+1;
printf("%d\n%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n%d %d\n",
6,a[i].x,a[i].y,a[i].x,X,X,X,X,-X,b[i].x,-X,b[i].x,b[i].y);
}
}
}
I. Sum of Palindromes
把一个长度为 的数字串分解为 个回文数之和。
每次取前一半,末位-1,然后翻转对应到后一半,然后相减,每次长度减少一半,只需要 次。
有一些特判,比如 -1 之后位数减少的情况,以及最后剩下 “10”。
Code:
#include<bits/stdc++.h>
#define maxn 100005
#define pb(x) push_back(x)
using namespace std;
int n;
char a[maxn],b[maxn];
vector<string>ans;
int main()
{
int T;
for(scanf("%d",&T);T--;){
scanf("%s",a),n=strlen(a);
ans.clear();
for(reverse(a,a+n);n;){
if(n==2&&a[1]=='1'&&a[0]=='0') {ans.pb("1"),ans.pb("9");break;}
if(n==1) {ans.pb(string(1,a[0]));break;}
int l=n/2;
for(int i=n-1;i>=l;i--) b[i]=a[i];
b[l]--;
for(int i=l;b[i]<'0';i++) b[i]+=10,b[i+1]--;
if(b[n-1]=='0'){
for(int i=0;i<l;i++) b[i]=b[n-2-i];
if(!(n&1)) b[l-1]='9';
ans.push_back(string(b,b+n-1));
}
else{
for(int i=0;i<l;i++) b[i]=b[n-1-i];
ans.push_back(string(b,b+n));
}
for(int i=0;i<n;i++) if((a[i]-=b[i]-'0')<'0') a[i]+=10,a[i+1]--;
while(n&&a[n-1]=='0') n--;
}
printf("%d\n",ans.size());
for(int i=0,lim=ans.size();i<lim;i++) printf("%s\n",ans[i].c_str());
}
}
H. Lighthouses
凸多边形,有座标,一些顶点之间的边,求最长的边不交叉的路径的长度。
因为是凸包,所以向某一边走后不可能再走向另外一边,区间DP,记 和 分别表示走左还是走右即可。枚举到达点转移,
Code:
#include<bits/stdc++.h>
#define maxn 305
using namespace std;
int n,m,X[maxn],Y[maxn];
bool e[maxn][maxn];
double ans,f[maxn][maxn],g[maxn][maxn],dis[maxn][maxn];
double Dist(int i,int j){return sqrt(1ll*(X[i]-X[j])*(X[i]-X[j])+1ll*(Y[i]-Y[j])*(Y[i]-Y[j]));}
int main()
{
int T;
for(scanf("%d",&T);T--;){
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&X[i],&Y[i]);
for(int i=0;i<n;i++) for(int j=0;j<n;j++) e[i][j]=0,dis[i][j]=Dist(i,j),f[i][j]=g[i][j]=0;
scanf("%d",&m);
for(int i=1,x,y;i<=m;i++) scanf("%d%d",&x,&y),x--,y--,e[x][y]=e[y][x]=1;
ans=0;
for(int d=2;d<n;d++)
for(int l=0;l<n;l++){
int r=(l+d)%n;
for(int k=(l+1)%n;k!=r;k=(k+1)%n){
double x=max(f[l][k],g[r][k]);
if(e[r][k]) f[l][r]=max(f[l][r],dis[r][k]+x);
if(e[l][k]) g[r][l]=max(g[r][l],dis[l][k]+x);
}
}
for(int l=0;l<n;l++) for(int r=0;r<n;r++) if(l!=r&&e[l][r])
ans=max(ans,dis[l][r]+max(f[l][r],g[l][r]));
printf("%.9f\n",ans);
}
}
A. Bags of Candies
一组可以包含两个不互质的数 或 一个单独的数,求将1到n最少分多少组。
答案显然为 最多配对数
设 的质数 的数量为 ,那么 以及 1 都只能分成单独的组,配对数的上界为
构造上界:
将剩下的数按照最大质因子分组,同一组的显然可以配对,如果组的大小为奇数那么会剩下一个数,对于最大质因子为 的组, 一定在这个组中,那么把让剩下的数为 ,最后剩下的数都含有 这个因子,继续配对即可达到上界。
于是就是求区间质数,官方题解用的是分段打表,剩下的一段用埃氏筛 。
我直接 Min_25 了,语言选 GNU G++17 9.2.0 (64 bit, msys 2) 才能过。。其它的会 T。。
开 O2 的话除法用浮点数乘法会快很多(3倍)。
Code:
#include<bits/stdc++.h>
#define maxn 1000005
#define LL long long
using namespace std;
int sn,m;
LL n,a[maxn],s[maxn];
inline int ID(LL x){return x<=sn?x:m-n/x+1;}
int main()
{
int T;
for(scanf("%d",&T);T--;){
scanf("%lld",&n),sn=sqrt(n),m=0;
for(LL i=1;i<=n;i++) a[++m]=i=n/(n/i),s[m]=i-1;
for(int i=2;i<=sn;i++) if(s[i]^s[i-1]){
double inv=1.0/i;
LL lim=1ll*i*i,sp=s[i-1];
for(int j=m;a[j]>=lim;j--)
s[j]-=s[ID((LL)(a[j]*inv+1e-9))]-sp;
}
printf("%lld\n",n-(n-(s[m]-s[ID(n/2)])-1)/2);
}
}
E. Contamination
平面直角座标系,给出 个圆, 次询问, 在纵座标的范围内是否存在一条不经过圆的路径(可以走实数)。 ,圆不相交。
因为圆不相交所以只可能是一个圆连通了上下边界,稍作分析相当于是问是否存在一个圆 满足 且
乍一看是个三维数点,但是这并不是个计数问题而是存在性问题,所以第三维的“数”可以变成求最值。
具体地说,按 从下到上扫描线,遇到 就在线段树的 位置加入 ,在 处遇到询问就查询线段树上 的最大值是否大于等于 即可。
Code:
#include<bits/stdc++.h>
#define maxn 1000005
#define maxp maxn*22
using namespace std;
const int inf = 1e9;
int n,m,px[maxn],qx[maxn],cnt;
bool ans[maxn];
struct node{
int y,my,x,id;
bool operator < (const node &p)const{return y==p.y?id<p.id:y<p.y;}
}q[maxn<<1];
int rt,lc[maxp],rc[maxp],mx[maxp],sz;
void ins(int &i,int l,int r,int x,int v){
if(!i) i=++sz,mx[i]=-inf-1;
mx[i]=max(mx[i],v);
if(l==r) return;
int mid=(l+r)>>1;
x<=mid?ins(lc[i],l,mid,x,v):ins(rc[i],mid+1,r,x,v);
}
int qry(int i,int l,int r,int x,int y){
if(!i) return -inf-1;
if(x<=l&&r<=y) return mx[i];
int mid=(l+r)>>1,ret=-inf-1;
if(x<=mid) ret=qry(lc[i],l,mid,x,y);
if(y>mid) ret=max(ret,qry(rc[i],mid+1,r,x,y));
return ret;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,x,y,r;i<=n;i++) scanf("%d%d%d",&x,&y,&r),q[++cnt]=(node){y-r,y+r,x,0};
for(int i=1;i<=m;i++) ++cnt,scanf("%d%*d%d%*d%d%d",&px[i],&qx[i],&q[cnt].y,&q[cnt].my),q[cnt].id=i;
sort(q+1,q+1+cnt);
for(int i=1;i<=cnt;i++)
if(!q[i].id) ins(rt,-inf,inf,q[i].x,q[i].my);
else {int l=px[q[i].id],r=qx[q[i].id]; ans[q[i].id]=qry(rt,-inf,inf,min(l,r),max(l,r))<q[i].my;}
for(int i=1;i<=m;i++) puts(ans[i]?"YES":"NO");
}
F. The Halfwitters
一个 的排列,用 的代价交换相邻数字, 的代价翻转整个排列, 的代价变为随机排列,问将排列变为 的最小期望代价。
操作可以增加或减少一个逆序对, 操作将逆序对数 变为
假设不用 ,那么有 个逆序对的最小代价为
设随机排列的期望代价为 ,那么真实答案 ,有
要代价尽量小,那么显然 越小越优,那么剩下就是找到 的最小可能值。
把 排序得到 ,那么肯定是前一部分选 ,后一部分选 ,枚举位置 ,那么有:
解出 ,如果满足 ,那么这个 就是合法的,取最小即可。实际上位置 满足单调性,可以二分。可以尝试作差比较证明。
Code:
#include<bits/stdc++.h>
#define maxn 155
#define LL long long
using namespace std;
int n,id[maxn],p[maxn];
LL cnt[18][maxn],fac[18],f[maxn];
bool cmp(int i,int j){return f[i]<f[j];}
int main()
{
int T,a,b,c,d;
cnt[0][0]=fac[0]=1;
for(int i=1;i<=16;i++) for(int j=0;j<=i*(i-1)/2;j++)
for(int k=0;k<=min(i-1,j);k++) cnt[i][j]+=cnt[i-1][j-k];
for(int i=1;i<=16;i++) fac[i]=fac[i-1]*i;
for(scanf("%d",&T);T--;){
scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
int N = n*(n-1)/2;
for(int i=0;i<=N;i++) f[i]=min(a*i,b+a*(N-i)),id[i]=i;
sort(id,id+N+1,cmp);
LL pre=0,suf=fac[n],fz,fm;
double X = 1e20;
for(int o=0;o<N;o++){
int i=id[o];
pre+=f[i]*cnt[n][i],suf-=cnt[n][i];
fm=fac[n]-suf,fz=pre+c*suf;
double x = 1.0*fz/fm;
if(x+c<=f[id[o+1]]) {X=x;break;}
}
fz+=c*fm; LL D=__gcd(fz,fm); fz/=D,fm/=D;
while(d--){
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
int k=0;
for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) if(p[i]>p[j]) k++;
if(f[k]<=X+c) printf("%lld/1\n",f[k]);
else printf("%lld/%lld\n",fz,fm);
}
}
}
J. Space Gophers
,的立方体小格,给出 条形如 的隧道,表示 为空。 同理,两个空格子有公共面视为连通,给 次询问 与 是否连通。
把隧道看做点。
对于形如 的点,有两种情况连通:
- 的位置相同, 周围四连通的点与其连通。
- 的位置不同,两个点都不为 的位置相差
连通用并查集维护。
查找第一种点用 解决。
查找第二种点用 ,表示的位置为,的位置为,座标为的点。如果一个点向某个 有连边,那么整个 都连通了,删去除第一个元素外的元素没有影响。
这样复杂度就是 的。常数很大,我加了fread才过。。
Code:
#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
char c;bool f=0;while(!isdigit(c=getc())) c=='-'&&(f=1);
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0'); f&&(a=-a);
}
int n,f[maxn],d[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
map<pair<int,int>,int>A[3];
vector<int>B[3][3][1000005];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void join(int x,int y){if(!y) return; f[find(x)]=find(y);}
void links(int x,vector<int>&y){
if(!y.empty()){
for(;y.size()>1;y.pop_back()) join(x,y.back());
join(x,y.back());
}
}
int get(int *a){
for(int i=0,id;i<3;i++) if(id=A[i][make_pair(a[(i+1)%3],a[(i+2)%3])]) return find(id);
}
int main()
{
int T,Q;
for(scanf("%d",&T);T--;){
read(n);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=0;i<3;i++){
A[i].clear();
for(int j=0;j<3;j++) if(i!=j) for(int k=1;k<=1000000;k++) B[i][j][k].clear();
}
for(int id=1;id<=n;id++){
int a[3]; read(a[0]),read(a[1]),read(a[2]);
for(int i=0;i<3;i++) if(a[i]==-1){
int x=a[(i+1)%3],y=a[(i+2)%3];
for(int j=0;j<4;j++) join(id,A[i][make_pair(x+d[j][0],y+d[j][1])]);
for(int j=-1;j<=1;j++) links(id,B[(i+2)%3][(i+1)%3][x+j]),links(id,B[(i+1)%3][(i+2)%3][y+j]);
A[i][make_pair(x,y)]=id;
B[i][(i+1)%3][x].push_back(id),B[i][(i+2)%3][y].push_back(id);
}
}
read(Q);
for(int a[3],b[3];Q--;){
read(a[0]),read(a[1]),read(a[2]),read(b[0]),read(b[1]),read(b[2]);
puts(get(a)==get(b)?"YES":"NO");
}
}
}
2018 Petrozavodsk Winter Camp, Yandex Cup
I. or
交互题。
个栈排成一排,大小都为 ,不知道栈中元素,每轮给出栈顶,你给出一个数 ,然后交互库给出一个符号 或 ,栈顶 的元素被弹出,进入下一轮,直到全栈为空。要求在50次内将栈清空。
记每个栈的大小为 ,每个栈的权值为 , 是设定的一个常数,记
注意到必然存在一个位置 ,使得左半部分和右半部分(均包含)的和都 。
所以将栈按照栈顶元素从小到大排序,然后找到位置 ,并令 等于这个栈的栈顶。那么无论给出的符号是小于等于还是大于等于,新的 必然
显然 越大 每次缩得越小,然而当 时为 ,而 ,已经足够在 50 次内完成。
Code:
#include<bits/stdc++.h>
#define maxn 10005
using namespace std;
int n,k,siz[maxn],pw[12],id[maxn],a[maxn];
bool cmp(int i,int j){return a[i]<a[j];}
int main()
{
scanf("%d%d",&n,&k);
for(int i=(pw[1]=3,2);i<=10;i++) pw[i]=pw[i-1]*3;
for(int i=1;i<=n;i++) siz[i]=k;
while(1){
for(int i=1;i<=n;i++) scanf("%d",&a[i]),id[i]=i;
sort(id+1,id+1+n,cmp);
int sum=0;
for(int i=1;i<=n;i++) sum+=pw[siz[i]];
int x=0;
for(int i=1;i<=n;i++)
if((x+=pw[siz[id[i]]])*2>=sum){
printf("%d\n",x=a[id[i]]); fflush(stdout);
break;
}
char op[4]; scanf("%s",op);
if(op[0]=='E') return 0;
if(op[0]=='<') {for(int i=1;i<=n;i++) if(siz[i]&&a[i]<=x) siz[i]--;}
else {for(int i=1;i<=n;i++) if(siz[i]&&a[i]>=x) siz[i]--;}
}
}
J. Stairways
到 从左到右排成一排,每个人有权值 ,将其分成两排,排内保持原来的顺序,如果对前后两个人 有 ,则要付出 的代价,并使 ,求最小代价。
每个人的代价相当于是前缀最大值 减去
在两排的前缀最大值确定的情况下后面的最优解就确定了,所以设 表示前 个数,第一排的前缀最大值为 ,第二排的前缀最大值为 时,后 放进去的最小代价。
考虑 放进第一排还是第二排以及 与 的大小关系进行转移。
- , 放进第二排最优,
- , 放进第二排有:
放进第一排有:
可能会有 的状态,但是这并不影响答案,最优解一定可以递推出来。
第一种操作是区间加等差数列,第二种操作是区间加然后对一个数 。
观察可以发现对固定的 , 随 增大单调不降,所以第二种操作可以二分出前一部分取 ,后一部分取 的位置,然后区间加、区间赋值即可。
查询需要单点查询,以及线段树二分。
线段树维护出区间左端点的值,区间加标记的首项公差,区间赋值标记即可。可以离散化。
Code:
#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;
int n,N,a[maxn],A[maxn],pre[maxn];
LL tag[maxn<<2];
struct data{
LL x,d,v; data(LL x=0,LL d=0,LL v=0):x(x),d(d),v(v){}
void operator += (const data &t){x+=t.x,d+=t.d,v+=t.x;}
}t[maxn<<2];
#define lc i<<1
#define rc i<<1|1
void down(int i,int L,int M){
if(~tag[i]) t[lc]=t[rc]=data(0,0,tag[i]),tag[lc]=tag[rc]=tag[i],tag[i]=-1;
if(t[i].x||t[i].d) t[lc]+=t[i],t[rc]+=data(t[i].x+(A[M+1]-A[L])*t[i].d,t[i].d),t[i].x=t[i].d=0;
}
void Add(int i,int l,int r,int x,int y,data v){
if(x<=l&&r<=y) return t[i]+=data(v.x+(A[l]-A[x])*v.d,v.d);
int mid=(l+r)>>1; down(i,l,mid);
if(x<=mid) Add(lc,l,mid,x,y,v);
if(y>mid) Add(rc,mid+1,r,x,y,v);
t[i].v=t[lc].v;
}
void Cov(int i,int l,int r,int x,int y,LL v){
if(x<=l&&r<=y) {t[i]=data(0,0,v),tag[i]=v;return;}
int mid=(l+r)>>1; down(i,l,mid);
if(x<=mid) Cov(lc,l,mid,x,y,v);
if(y>mid) Cov(rc,mid+1,r,x,y,v);
t[i].v=t[lc].v;
}
int find(int i,int l,int r,LL x){
if(l==r) return t[i].v<=x?l:l-1;
int mid=(l+r)>>1; down(i,l,mid);
return t[rc].v<=x?find(rc,mid+1,r,x):find(lc,l,mid,x);
}
LL qry(int i,int l,int r,int x){
if(l==r) return t[i].v;
int mid=(l+r)>>1; down(i,l,mid);
return x<=mid?qry(lc,l,mid,x):qry(rc,mid+1,r,x);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),A[i]=a[i];
sort(A+1,A+1+n),N=unique(A+1,A+1+n)-A-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(A+1,A+1+N,a[i])-A,pre[i]=max(pre[i-1],a[i]);
memset(tag,-1,sizeof tag);
for(int i=n;i>=1;i--){
LL now=qry(1,1,N,a[i]);
int p=min(a[i]-1,find(1,1,N,now-(A[pre[i]]-A[a[i]])));
if(p) Add(1,1,N,1,p,data(A[pre[i]]-A[a[i]],0));
if(p<a[i]-1) Cov(1,1,N,p+1,a[i]-1,now);
Add(1,1,N,a[i],N,data(0,1));
}
printf("%lld\n",qry(1,1,N,1));
}