• Codeforces LATOKEN Round 1 (Div. 1 + Div. 2)


    F1. Falling Sand

    题目描述

    点此看题

    (n imes m) 的方格,其中#代表沙子,.代表空格,你可以每次操作可以任意选择一个沙子使之自由落体,和这个沙子下落路径有边相邻的沙子也会下落,问让所有沙子下落的最小操作数。

    (1leq ncdot mleq 400000)

    解法

    首先大概分析下这个问题,一个沙子的下落会引起连锁反应,这时候用图论表达这个过程是最好的。

    (i)(j) 连边表示 (i) 的下落会带动 (j) 的下落,要根据下落的路径把有关的点都连起来,但实际上只用连这四条边即可:

    • 如果这个点上方一格有点,那么连边。
    • 如果这个点下方有点,那么连边。
    • 找到左侧第一个在它下面的点连边。
    • 找到右侧第一个在它下面的点连边。

    问题就转化到这张图上了,我们可以先 ( t tarjan) 缩点,然后直接选入度为 (0) 的点即可。

    F2.Falling Sand

    题目描述

    点此看题

    相对于简单版本,你需要让第 (i) 列有 (a_i) 个沙子下落。

    解法

    不妨考虑和简单版本有什么变化,我们不用让所有沙子下落。因为同一列高的下落矮的一定下落,可以把每一行的限制简化成让第 (a_i) 个沙子下落即可,还可以简化,如果在建出的图中某个关键点能到达另一个关键点,那么可以忽略被到达点的限制。

    现在回到图上思考这个问题,选取一个点等价于覆盖它能到达的所有关键点,最后的目的是让所有关键点都被覆盖。直接是做不动的,但是这个覆盖体现在原来的矩阵中是倾向于覆盖更相邻的列的,我们不禁要想,覆盖是否有区间性质?

    也就是某个点覆盖的关键点一定在矩阵中构成一段连续的区间,证明用反证法:考虑三列 (u<v<w),如果能通过操作第 (u) 列解决第 (w) 列的限制,那么一定会经过 (v) 列中的一个点 (i),因为不能解决 (v) 的限制,所以 (v) 的关键点高于 (i),进而推出 (v) 的关键点可以到达 (w) 的关键点,这与我们简化后的问题矛盾,这里是不能存在到达关系的。

    那么问题变成了选取若干个区间的并集是全集,可以考虑贪心,我们把所有区间按左端点排序,然后顺次扫描,如果当前必须选一个区间(也就是下一个区间的左端点大于现在右端点 (+1),不选就会出现空档),那么就选一个最大的右端点,时间复杂度 (O(nmlog nm))

    注意一定要按照列的顺序来给关键点编号,而且缩过点的关键点一定不能重复编号。

    总结

    只有你尽量的去简化问题,神秘的性质才会赐福于你。

    #include <cstdio>
    #include <vector>
    #include <assert.h>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <stack>
    using namespace std;
    const int M = 400005;
    const int inf = 0x3f3f3f3f;
    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,tot,cnt,ans,Ind,zxy,f[M],a[M],val[M];
    int low[M],dfn[M],in[M],col[M],d[M],key[M],p[M];
    vector<int> v[M],g[M],o;char s[M];
    pair<int,int> sq[M];stack<int> st;
    struct edge
    {
    	int v,next;
    }e[4*M];
    int id(int x,int y)
    {
    	return (x-1)*m+y;
    }
    void add(int u,int v)
    {
    	e[++tot]=edge{v,f[u]},f[u]=tot;
    }
    void tarjan(int u)
    {
    	dfn[u]=low[u]=++Ind;in[u]=1;
    	st.push(u);
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(!dfn[v])
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(in[v])
    			low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		int v;cnt++;
    		do
    		{
    			v=st.top();st.pop();
    			in[v]=0;col[v]=cnt;
    		}while(u!=v);
    	}
    }
    void tpsort()
    {
    	queue<int> q;
    	for(int i=1;i<=cnt;i++)
    		if(!d[i]) q.push(i);
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		if(key[u]) in[u]=1;
    		o.push_back(u);
    		for(int i=0;i<g[u].size();i++)
    		{
    			int v=g[u][i];d[v]--;
    			if(in[u]) in[v]=1,key[v]=0;
    			if(!d[v]) q.push(v);
    		}
    	}
    	for(int i=1;i<=cnt;i++)
    		sq[i]=make_pair(inf,-inf);
    	for(int i=1;i<=m;i++)//in this order
    		if(key[p[i]] && sq[p[i]].first==inf)
                // I WA for this for thousands of time
    			zxy++,sq[p[i]]=make_pair(zxy,zxy);
    	int len=o.size();
    	for(int i=len-1;i>=0;i--)
    	{
    		int u=o[i];
    		for(int j=0;j<g[u].size();j++)
    		{
    			int v=g[u][j];
    			sq[u].first=min(sq[u].first,sq[v].first);
    			sq[u].second=max(sq[u].second,sq[v].second);
    		}
    	}
    	sort(sq+1,sq+1+cnt);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%s",s+1);
    		for(int j=1;j<=m;j++)
    			if(s[j]=='#')
    			{
    				a[id(i,j)]=++k;
    				v[j].push_back(i);
    			}
    	}
    	for(int i=1;i<=m;i++) val[i]=read();
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=0;j<v[i].size();j++)
    		{
    			int u=a[id(v[i][j],i)];
    			if(j && v[i][j-1]+1==v[i][j])
    				add(u,a[id(v[i][j-1],i)]);
    			if(j+1<v[i].size())
    				add(u,a[id(v[i][j+1],i)]);
    			if(i>1)
    			{
    				auto t=lower_bound(v[i-1].begin(),v[i-1].end(),v[i][j]);
    				if(t!=v[i-1].end()) add(u,a[id(*t,i-1)]);
    			}
    			if(i<m)
    			{
    				auto t=lower_bound(v[i+1].begin(),v[i+1].end(),v[i][j]);
    				if(t!=v[i+1].end()) add(u,a[id(*t,i+1)]);
    			}
    		}
    	}
    	for(int i=1;i<=k;i++)
    		if(!dfn[i]) tarjan(i);
    	for(int i=1;i<=m;i++)
    	{
    		int t=v[i].size()-val[i];
    		if(t<v[i].size() && t>=0)
    		{
    			p[i]=col[a[id(v[i][t],i)]];
    			key[p[i]]=1;
    			assert(p[i]<=cnt);
    		}
    	}
    	for(int i=1;i<=k;i++)
    		for(int j=f[i];j;j=e[j].next)
    		{
    			int v=e[j].v;
    			if(col[i]!=col[v])
    			{
    				g[col[i]].push_back(col[v]);
    				d[col[v]]++;
    			}
    		}
    	tpsort();
    	int now=0,r=0;
    	for(int i=1;i<=cnt && now<zxy;i++)
    	{
    		r=max(r,sq[i].second);
    		if(i==cnt || sq[i+1].first>now+1)
    			ans++,now=r;
    	}
    	printf("%d
    ",ans);
    }
    

    G. A New Beginning

    题目描述

    点此看题

    有一个二维平面和 (n) 个人,阿七一开始在 ((0,0)),每次可以向上或者向右走一格,如果当前在 ((a,b)) 那么给 ((x_i,y_i)) 的人剪头发的代价是 (max(|x_i-a|,|y_i-b|)),最强发型师阿七想给每个人都剪一次头发,请你求出最小的代价。

    (1leq nleq 8cdot 10^5,0leq x_i,y_ileq 10^9)

    解法

    首先把 slope trick 看了吧,这里还有我的翻译博客

    假设我们已经知道了阿七的移动路径,那么怎么确定每个人什么时候让阿七给他剪头发呢?注意到这道题代价的计算方式不是曼哈顿距离,而是相邻两个横纵坐标差值的较大值,我们可以同时增大横纵坐标看和路径有没有交,这样可以得出结论:直线 (y=-x+(x_i+y_i)) 和路径的交点就是阿七剪头发的位置。

    证明这个结论也不难,画画图就行了,这里就略去证明过程。

    那么也就是说当阿七移动到 (a+b=x_i+y_i) 就会给第 (i) 个人剪头发,我们把这个平面旋转 (45) 度,那么每个人的坐标就变成了 ((x+y,x-y)),每次可以走到 ((a+1,b-1))((a+1,b+1)),然后我们考察 (y) 坐标的差值即可,最后答案要除以 (2)

    然后可以设计一个 (dp),设 (dp[a][b]) 表示走到 ((a,b)) 并且给所有 (xleq a) 的人剪头发的最小花费,转移:

    [dp[a][b]=min(dp[i][j]+|b-y_k|),b-(a-i)leq jleq b+(a-i) ]

    但是这样就稳 (T) 了,观察转移可以发现其实有两种操作:和某一个绝对值函数合并;范围取 (min) 更新单点;可以考虑用 ( t slopespace trick) 的方法,把 (dp[a]) 当成一个折线函数来维护。

    不难发现 (0) 斜率的折线是最优解存在的地方,那么可以把左右边分开,左边斜率 (<0),右边斜率大于 (>0)在范围取 (min) 更新单点的时候两边的函数值都会向中间收拢,转折点会根据 (0) 斜率线发散,这和 (a) 的差值是有关的,所以我们可以打标记,左边的转折点在插入的时候加 (a),在取出转折点的时候减 (a),右边加减对换即可,搞两个优先队列 (L,R) 维护转折点。

    新增一个对转折点根据 (0) 斜率线发散的解释。

    (a) 的差值是 (x),设 (l,r) 分别为 (0) 斜率线的左右端点,那么 ([l-x,l)) 都会变成最小值,同理 ((r,r+x]) 也会变成最小值,这导致了 (0) 斜率线的左右端点会发散。类似的,对于其他的转折点也会发散,就是因为向中间取最小值的操作。

    现在解决了第二个问题,第一个问题要考虑斜率的变化,这对优先队列里的元素有影响,分情况讨论:

    • 如果 (0) 斜率线的左端点 (>y),单个人 (y) 处斜率会由 (-1)(1),这个可以直接合并,所以把 (y) 插进 (l) 两次即可,但这会导致左端点(也就是 (L) 中最大的点)不再属于 (L),这时候要把它弹掉并且插入到 (R) 中去。
    • 如果 (0) 斜率线的右端点 (<y),右端点是 (R) 中最小的点,类似情况一操作即可。
    • 如果 (y)(0) 斜率线上,那么在 (L,R) 中都插入 (y) 来更新 (0) 斜率线。

    在上述操作中维护 (0) 斜率线的函数值,时间复杂度 (O(nlog n))

    总结

    根据代价的特性可以观察出神奇性质。

    线性函数可以考虑 slope trick 哦。

    slope trick 的区间取 (min) 可以通过打平移标记来实现。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    using namespace std;
    #define pii pair<int,int>
    #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,shift,ans;vector<pii> v;
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		int x=read(),y=read();
    		v.push_back(make_pair(x+y,x-y));
    	}
    	sort(v.begin(),v.end());
    	priority_queue<int> L;
    	priority_queue<int,vector<int>,greater<int>> R;
    	L.push(0);R.push(0);
    	for(int i=0;i<v.size();i++)
    	{
    		int x=v[i].first,y=v[i].second;shift=x;
    		int l=L.top()-shift,r=R.top()+shift;
    		if(l>y)
    		{
    			L.push(y+shift);L.push(y+shift);
    			L.pop();R.push(l-shift);
    			ans+=l-y;
    		}
    		else if(y>r)
    		{
    			R.push(y-shift);R.push(y-shift);
    			R.pop();L.push(r+shift);
    			ans+=y-r;
    		}
    		else L.push(y+shift),R.push(y-shift);
    	}
    	printf("%lld
    ",ans/2);
    }
    
  • 相关阅读:
    bzoj3033: 太鼓达人
    CH Round #24
    王志明:编辑部不送审,把你的投稿直接拒掉了,怎么办?
    用TinyXml做XML解析示例 TinyXml查找唯一节点及修改节点操作
    QT XML文档的解析 QXmlStreamReader, DOM,SAX 三种解析方法 简单示例
    QT 使用QUdpSocket QUdpServer UDP 建立客户端与服务器端
    QT 使用QTcpServer QTcpSocket 建立TCP服务器端 和 客户端
    Qt QThread 线程创建,线程同步,线程通信 实例
    Qt QT的IO流 QT输入输出
    Qt QSortFilterProxyModel示例代码, 使用方法
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14908361.html
Copyright © 2020-2023  润新知