[提高组互测] Day5
总结
这次没有挂分,不过还是被淡随切吊打了。
任何题最基本的问题转化都要有,思维步骤都有共通之处,不要只会做序列题啊。
把题想简单一点,相信自己的实力都可以切,毕竟我切过 \(3400\) 的题啊!
保留环节:感谢 \(\tt crashed\) 大佬的精心准备,虽然他的电脑因为蓝屏必须重新造数据
[CCO 2019] Sirtet
题目描述
解法
\(\tt naive\) 的题意转化:我们求出每个点的下落距离就可以知道最终状态。
对于初始四联通的点集,我们把他们连接起来当作一个整体,那么它们的下落距离是相同的。
考虑对于每个有物体的格子找到它下面第一个物品,设它的颜色是 \(x\)(每个点集颜色相同):
- 如果颜色相同,那么可以忽略。
- 如果不存在这样一个物品,那么 $d_x\leq $ 到底部的距离。
- 如果存在这样一个物品,设其颜色是 \(y\),那么 \(d_x\leq d_y+\) 它们的距离。
显然是一个差分约束问题,建出来图之后直接跑即可,比省选的那道差分约束板多了。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 1000005;
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,a[M],b[M],c[M],tot,f[M];char s[M];
int dis[M],ans[M];
int id(int x,int y) {return m*(x-1)+y;}
struct edge
{
int v,c,next;
}e[2*M];
struct node
{
int u,c;
bool operator < (const node &b) const
{
return c>b.c;
}
};priority_queue<node> q;
void dfs(int x,int y)
{
if(!a[id(x,y)] || b[id(x,y)]) return ;
b[id(x,y)]=k;
if(x>1) dfs(x-1,y);
if(x<n) dfs(x+1,y);
if(y>1) dfs(x,y-1);
if(y<m) dfs(x,y+1);
}
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
}
void dijkstra()
{
dis[k+1]=0;
q.push(node{k+1,0});
while(!q.empty())
{
int u=q.top().u;
int w=q.top().c;q.pop();
if(dis[u]<w) continue;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(dis[v]>dis[u]+c)
{
dis[v]=dis[u]+c;
q.push(node{v,dis[v]});
}
}
}
}
int main()
{
//freopen("tpt.in","r",stdin);
//freopen("tpt.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
for(int j=1;j<=m;j++)
a[id(i,j)]=(s[j]=='#');
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[id(i,j)] && !b[id(i,j)])
k++,dfs(i,j);
memset(dis,0x3f,sizeof dis);
for(int i=n;i>=1;i--)
for(int j=1;j<=m;j++) if(a[id(i,j)])
{
int w=b[id(i,j)],p=id(c[j],j);
if(!c[j])//nothing down below
add(k+1,w,n-i);
else if(b[p]!=w)//not the same color
add(b[p],w,c[j]-i-1);
c[j]=i;
}
dijkstra();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x=id(i,j);
if(a[x]) ans[id(i+dis[b[x]],j)]=a[x];
}
for(int i=1;i<=n;i++,puts(""))
for(int j=1;j<=m;j++)
{
if(ans[id(i,j)]) putchar('#');
else putchar('.');
}
}
「ROI 2019 Day2」课桌
题目描述
解法
这道题的结论很多都是感受出来得分,不给证明,现在对追求严谨的我真是一种煎熬啊
结论1:将学生按升高排序之后相邻两个配对最优。
结论2:把课桌升序排序之后按顺序分给学生最优。这里稍微解释一下,首先我们要除去课桌有包含的情况,那么按左端点排序之后满足左端点单增,右端点单增,这样就能区分顺序了。
那么直接把 \(m\) 组的相邻学生绑定起来称为一块,那么一共有 \(n\) 块学生,对于每一块可以单独做,计算每个课桌的代价时可以搞一个双指针,时间复杂度 \(O(nm+mk)\)
复杂度爆炸的原因是把每一块学生割裂开来了,利用第二个性质,每一块学生对应的最优决策点是单调不降的。这时候可以无脑上决策单调性分治了,每次求第 \(mid\) 块的最优匹配点,然后左右的决策点范围是 \([ql,mid]\) 和 \([mid,qr]\),时间复杂度 \(O((k+nm)\log m)\)
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int M = 400005;
#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,k,ans,b[M];vector<int> a[M];
struct node
{
int l,r;
bool operator < (const node &b) const
{
return l<b.l;
}
}s[M];
void cdq(int l,int r,int ql,int qr)
{
if(l>r) return ;
int x=(l+r)>>1;
int pre=0,suf=0,j=0,k=0,res=1e15,mid=ql;
int len=a[x].size(),nl=0,nr=len;
for(auto y:a[x]) suf+=y;
for(int i=ql;i<=qr;i++)
{
if(s[i].r<=s[i-1].r) continue;//useless
while(j<len && a[x][j]<=s[i].l)
{
pre+=a[x][j];
j++;nl++;
}
while(k<len && a[x][k]<s[i].r)
{
suf-=a[x][k];
k++;nr--;
}
int tmp=(suf-s[i].r*nr)+(s[i].l*nl-pre);
if(tmp<res) mid=i,res=tmp;
}
ans+=res;
cdq(l,x-1,ql,mid);
cdq(x+1,r,mid,qr);
}
signed main()
{
m=read();n=read();k=read();
for(int i=1;i<=k;i++)
s[i].l=read(),s[i].r=read();
sort(s+1,s+1+k);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=2*n;j++)
b[j]=read();
sort(b+1,b+1+2*n);
for(int j=1;j<=n;j++)
{
a[j].push_back(b[2*j-1]);
a[j].push_back(b[2*j]);
}
}
for(int i=1;i<=n;i++)
sort(a[i].begin(),a[i].end());
cdq(1,n,1,k);
printf("%lld\n",ans);
}