ACM寒假集训部分题目总结(2018-02-25——2018-03-02)
持续更新
Carpet
题目来源
https://vjudge.net/contest/213750#problem/C
http://codeforces.com/gym/101611/problem/C
题目大意
给你1000 000 × 20的格子,把一棵n(n<=100000)个点的树摆在格子图上,每个格子最多对应一个节点,边不交叉,构造一个方案。
题解
格点图的宽度是log级的,考虑套个数据结构。
注意到树链剖分的时候每个节点到根的路径上经过最多的轻链个数小于log(n)。
所以直接上树剖,每一层把重链扔右边,轻链扔下一层就好了。
代码
#include<bits/stdc++.h>
#define N 100005
#define M 22
using namespace std;
int n,X[N],Y[N],q[N],size[N],next[N];
int k,la[N],ff[N*2],fa[N],cnt[M],flag[N];
struct node{int a,b;}e[N*2];
void add(int a,int b)
{
e[++k]=(node){a,b};ff[k]=la[a];la[a]=k;
e[++k]=(node){b,a};ff[k]=la[b];la[b]=k;
}
void bfs()
{
int l=1,r=2;q[1]=1;size[1]=1;
while(l<r)
{
int x=q[l++];
for(int a=la[x];a;a=ff[a])
if(!size[e[a].b])
q[r]=e[a].b,fa[q[r]]=x,size[q[r]]=1,r++;
}
for(int i=r-1;i;i--)
{
int x=q[i];if(fa[x])size[fa[x]]+=size[x];
if(size[next[fa[x]]]<size[x])next[fa[x]]=x;
}
}
void dfs(int x,int y)
{
X[x]=++cnt[y];Y[x]=y;
for(int a=la[x];a;a=ff[a])
if(e[a].b!=fa[x]&&e[a].b!=next[x])
dfs(e[a].b,y+1);
if(next[x])dfs(next[x],y);
}
int main()
{
int a,b;
scanf("%d",&n);
for(int i=1;i<n;i++)
scanf("%d%d",&a,&b),add(a,b);
bfs();dfs(1,1);
for(int i=1;i<=n;i++)printf("%d %d\n",X[i],Y[i]);
return 0;
}
Infinite Gift
题目来源
https://vjudge.net/contest/213750#problem/I
http://codeforces.com/gym/101611/problem/I
题目大意
给n个k维向量,k维空间上的每个整点都会发出这n个向量,题目保证整张图任意两个整点联通。
问k维空间上所有整点是否构成二分图。n,k<=1500。
题解
二分图,有奇环,任意奇数个向量之和为0
如果是有环的话直接列方程,最右边放一排0,高斯消元就好了。
考虑到奇环,所以构成零向量的向量个数必须为奇数个。
题目有一个条件,k维空间上的任意两点联通。
举个例子,(1,0)这个点一定可以从(0,0)到达,而(2,0)也就肯定可以从(1,0)用同样的方式到达。
这样的话,(0,0)到(2,0)的边数就一定是偶数。
其他维同理。
所以对于每个k维向量我们只关心它的奇偶性。
比如(2,3,4)就等价于(0,1,0)
在方程最下面放一排1,如果有奇环那么选出来的这些向量线性组合形成的新向量最后一维异或值一定是1。
解异或方程组,用bitset加速优化一下就好了。
代码
#include<bits/stdc++.h>
#define N 1505
using namespace std;
int n,k;
bitset<N>s[N];
bool check(int x)
{
for(int i=1;i<n;i++)
if(s[x][i])return true;
return !s[x][n];
}
bool gauss()
{
for(int i=1,p=1;i<=n;i++,p++)
{
if(!s[p][i])
{
int pos=0;
for(int j=p+1;j<=k;j++)
if(s[j][i]){pos=j;break;}
if(pos)swap(s[p],s[pos]);
}
int flag=0;
for(int j=p+1;j<=k;j++)
if(s[j][i])s[j]^=s[p],flag=1;
if(!flag)p--;
}
for(int i=1;i<=k;i++)
if(!check(i))return false;
return true;
}
int main()
{
int x;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
scanf("%d",&x),s[j][i]=(x&1);
n++;k++;
for(int i=1;i<=n;i++)s[k][i]=1;
if(gauss())printf("No\n");
else printf("Yes\n");
return 0;
}
Famous Pagoda
题目来源
https://vjudge.net/contest/214026#problem/F
https://open.kattis.com/problems/famouspagoda
题目大意
给n个数,将其分成m段,每段的值为 (其中v为任意整数,i到j为一段)
求总值最小。n,m<=2000,k=1或2。
题解
一个显然的dp式:f[i]=min(f[j]+g[j+1][i]),g[i][j]表示i到j这一段的代价。
g[i][j]可以先预处理出来,k=1的话v取i到j的中位数,k=2的话那个式子可以搞成一个二次函数,O(1)求极值就好了。
根据观察(雾),这个dp式满足决策单调性,但是不能斜率优化。
有两种做法:
第一种:枚举每个决策点,维护一个栈,每次找到第一个当前决策点不是最优的区间,在该区间二分决策点的段点。时间复杂度O(nlogn)。
第二种:考虑分治。对于每一层区间[l,r],决策点区间[lx,rc]。每次暴力在[lx,rx]中求出mid点的决策点j,那么[l,mid-1]区间的决策点必然在[lx,j],[mid+1,r]区间的决策点区间必然在[j,rx]。往下分治,每层暴力的时间复杂度之和是O(n),总复杂度O(nlogn)。
代码
第一种做法
#include<bits/stdc++.h>
#define ll long long
#define inf 999999999999999999
#define N 2005
using namespace std;
int n,m,k,s[N];
ll sum1[N],sum2[N],g[N][N],f[N][N],A[N],B[N];
ll cal(ll x,ll a,ll b,ll c)
{
return (ll)a*x*x+(ll)b*x+c;
}
ll F(int k,int i,int j)
{
return f[k][j-1]+g[k+1][i];
}
int main()
{
int pos,top,po;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
for(int i=1;i<=n;i++)sum1[i]=sum1[i-1]+s[i];
for(int i=1;i<=n;i++)sum2[i]=sum2[i-1]+(ll)s[i]*s[i];
//sum1:sum a sum2:sum a^2
if(k==1)
{
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(j-i+1==1){g[i][j]=0;continue;}
if(j-i+1==2){g[i][j]=s[j]-s[i];continue;}
if((j-i)&1)
{
pos=i+(j-i+1)/2-1;
g[i][j]=sum1[j]-sum1[pos]-(sum1[pos]-sum1[i-1]);
}
else
{
pos=i+j>>1;
g[i][j]=sum1[j]-sum1[pos]-(sum1[pos-1]-sum1[i-1]);
}
}
}
else
{
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
ll a=j-i+1;
ll b=(sum1[j]-sum1[i-1])*2;
ll c=sum2[j]-sum2[i-1];
ll pos=b/(2*a);
g[i][j]=min(cal(s[i],a,-b,c),cal(s[j],a,-b,c));
if(s[i]<pos&&pos<s[j])g[i][j]=min(g[i][j],cal(pos,a,-b,c));
pos++;
if(s[i]<pos&&pos<s[j])g[i][j]=min(g[i][j],cal(pos,a,-b,c));
}
}
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)f[i][j]=inf;
f[0][0]=0;
for(int j=1;j<=m;j++)
{
top=1;A[1]=j-1;B[1]=j;
for(int i=j;i<=n;i++)
{
while(top>1&&F(A[top],B[top],j)>=F(i,B[top],j))top--;
int l=B[top]+1,r=n+1;
while(r-l>1)
{
int mid=l+r>>1;
if(F(i,mid,j)<F(A[top],mid,j))r=mid;
else l=mid+1;
}
po=F(i,l,j)<F(A[top],l,j)?l:r;
if(po<n+1)A[++top]=i,B[top]=po;
}
for(int i=j,k=1;i<=n;i++)
{
while(B[k+1]<=i&&k<top)k++;
f[i][j]=F(A[k],i,j);
}
}
printf("%lld\n",f[n][m]);
return 0;
}
第二种做法
#include<bits/stdc++.h>
#define ll long long
#define inf 999999999999999999
#define N 2005
using namespace std;
int n,m,k,s[N];
ll sum1[N],sum2[N],g[N][N],f[N][N];
ll cal(ll x,ll a,ll b,ll c)
{
return (ll)a*x*x+(ll)b*x+c;
}
void solve(int l,int r,int lx,int rx,int j)
{
if(l>r)return;
int mid=l+r>>1,pos=-1;
for(int i=lx;i<=rx&&i<mid;i++)
if(f[mid][j]>f[i][j-1]+g[i+1][mid])
f[mid][j]=f[i][j-1]+g[i+1][mid],pos=i;
solve(l,mid-1,lx,pos,j);
solve(mid+1,r,pos,rx,j);
}
int main()
{
int pos;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
for(int i=1;i<=n;i++)sum1[i]=sum1[i-1]+s[i];
for(int i=1;i<=n;i++)sum2[i]=sum2[i-1]+(ll)s[i]*s[i];
//sum1:sum a sum2:sum a^2
if(k==1)
{
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(j-i+1==1){g[i][j]=0;continue;}
if(j-i+1==2){g[i][j]=s[j]-s[i];continue;}
if((j-i)&1)
{
pos=i+(j-i+1)/2-1;
g[i][j]=sum1[j]-sum1[pos]-(sum1[pos]-sum1[i-1]);
}
else
{
pos=i+j>>1;
g[i][j]=sum1[j]-sum1[pos]-(sum1[pos-1]-sum1[i-1]);
}
}
}
else
{
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
ll a=j-i+1;
ll b=(sum1[j]-sum1[i-1])*2;
ll c=sum2[j]-sum2[i-1];
ll pos=b/(2*a);
g[i][j]=min(cal(s[i],a,-b,c),cal(s[j],a,-b,c));
if(s[i]<pos&&pos<s[j])g[i][j]=min(g[i][j],cal(pos,a,-b,c));
pos++;
if(s[i]<pos&&pos<s[j])g[i][j]=min(g[i][j],cal(pos,a,-b,c));
}
}
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)f[i][j]=inf;
f[0][0]=0;
for(int j=1;j<=m;j++)solve(j,n,j-1,n,j);
printf("%lld\n",f[n][m]);
return 0;
}
Dropping Ball
题目来源
https://vjudge.net/contest/214026#problem/D
题目大意
在一个如图的轨道里从最上层扔的某个位置扔球,问滚到最下层的位置。如果滚不到最下层输出-1。
还有一个修改操作,每次改变一个格子的轨道方向。
n*m<=100000
题解
分两种情况考虑
对于m比较小的情况,每次修改O(1),询问O(m)暴力查询就好了。
对于m比较大的情况,在m这维建线段树。线段树上的每个节点存一个输出s[i]表示从i格出发经过线段树上的这段区间会到的格子。
预处理O(n*m*logm),修改O(logm),查询O(1)。
代码
#include<bits/stdc++.h>
#define N 100005
#define M 205
using namespace std;
int n,m,Q;
vector<int>s[N];
struct node{int s[M];}t[N*4];
void solve1()
{
int tp,x,y;
while(Q--)
{
scanf("%d",&tp);
if(tp==1)scanf("%d%d",&x,&y),s[x][y]^=1;
else
{
scanf("%d",&x);
for(int i=1;i<=m;i++)
{
if(s[i][x])
{
if(!s[i][x-1]){x=-1;break;}
else x--;
}
else
{
if(s[i][x+1]){x=-1;break;}
else x++;
}
}
printf("%d\n",x);
}
}
}
class seg_tree
{
void update(int x)
{
int lc=x<<1,rc=lc+1;
for(int i=1;i<=n;i++)
{
if(t[lc].s[i]==-1)t[x].s[i]=-1;
else t[x].s[i]=t[rc].s[t[lc].s[i]];
}
}
public:
void build(int x,int l,int r)
{
if(l==r)
{
for(int i=1;i<=n;i++)
{
if(s[l][i])
{
if(!s[l][i-1])t[x].s[i]=-1;
else t[x].s[i]=i-1;
}
else
{
if(s[l][i+1])t[x].s[i]=-1;
else t[x].s[i]=i+1;
}
}
return;
}
int mid=l+r>>1,lc=x<<1,rc=lc+1;
build(lc,l,mid);build(rc,mid+1,r);
update(x);
}
void modify(int x,int l,int r,int X,int Y)
{
if(l==r)
{
s[X][Y]^=1;
for(int i=max(Y-1,1);i<=min(Y+1,n);i++)
{
if(s[X][i])
{
if(!s[X][i-1])t[x].s[i]=-1;
else t[x].s[i]=i-1;
}
else
{
if(s[X][i+1])t[x].s[i]=-1;
else t[x].s[i]=i+1;
}
}
return;
}
int mid=l+r>>1,lc=x<<1,rc=lc+1;
if(X<=mid)modify(lc,l,mid,X,Y);
else modify(rc,mid+1,r,X,Y);
update(x);
}
}T;
void solve2()
{
int tp,x,y;
T.build(1,1,m);
while(Q--)
{
scanf("%d",&tp);
if(tp==1)
{
scanf("%d%d",&x,&y);
T.modify(1,1,m,x,y);
}
else
{
scanf("%d",&x);
printf("%d\n",t[1].s[x]);
}
}
}
int main()
{
char ch;
scanf("%d%d%d",&m,&n,&Q);
for(int i=1;i<=m;i++)
{
s[i].push_back(0);
for(int j=1;j<=n;j++)
scanf(" %c",&ch),s[i].push_back(ch=='/');
s[i].push_back(1);
}
if(m<=500)solve1();
else solve2();
return 0;
}