1. 程式人生 > 其它 >Codeforces LATOKEN Round 1 (Div. 1 + Div. 2)

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

F1. Falling Sand

題目描述

點此看題

\(n\times m\) 的方格,其中#代表沙子,.代表空格,你可以每次操作可以任意選擇一個沙子使之自由落體,和這個沙子下落路徑有邊相鄰的沙子也會下落,問讓所有沙子下落的最小運算元。

\(1\leq n\cdot m\leq 400000\)

解法

首先大概分析下這個問題,一個沙子的下落會引起連鎖反應,這時候用圖論表達這個過程是最好的。

\(i\)\(j\) 連邊表示 \(i\) 的下落會帶動 \(j\) 的下落,要根據下落的路徑把有關的點都連起來,但實際上只用連這四條邊即可:

  • 如果這個點上方一格有點,那麼連邊。
  • 如果這個點下方有點,那麼連邊。
  • 找到左側第一個在它下面的點連邊。
  • 找到右側第一個在它下面的點連邊。

問題就轉化到這張圖上了,我們可以先 \(\tt tarjan\) 縮點,然後直接選入度為 \(0\) 的點即可。

F2.Falling Sand

題目描述

點此看題

相對於簡單版本,你需要讓第 \(i\) 列有 \(a_i\) 個沙子下落。

解法

不妨考慮和簡單版本有什麼變化,我們不用讓所有沙子下落。因為同一列高的下落矮的一定下落,可以把每一行的限制簡化成讓第 \(a_i\) 個沙子下落即可,還可以簡化,如果在建出的圖中某個關鍵點能到達另一個關鍵點,那麼可以忽略被到達點的限制。

現在回到圖上思考這個問題,選取一個點等價於覆蓋它能到達的所有關鍵點,最後的目的是讓所有關鍵點都被覆蓋。直接是做不動的,但是這個覆蓋體現在原來的矩陣中是傾向於覆蓋更相鄰的列的,我們不禁要想,覆蓋是否有區間性質?

也就是某個點覆蓋的關鍵點一定在矩陣中構成一段連續的區間,證明用反證法:考慮三列 \(u<v<w\),如果能通過操作第 \(u\) 列解決第 \(w\) 列的限制,那麼一定會經過 \(v\) 列中的一個點 \(i\),因為不能解決 \(v\) 的限制,所以 \(v\) 的關鍵點高於 \(i\),進而推出 \(v\) 的關鍵點可以到達 \(w\) 的關鍵點,這與我們簡化後的問題矛盾,這裡是不能存在到達關係的。

那麼問題變成了選取若干個區間的並集是全集,可以考慮貪心,我們把所有區間按左端點排序,然後順次掃描,如果當前必須選一個區間(也就是下一個區間的左端點大於現在右端點 \(+1\),不選就會出現空檔),那麼就選一個最大的右端點,時間複雜度 \(O(nm\log 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\n",ans);
}

G. A New Beginning

題目描述

點此看題

有一個二維平面和 \(n\) 個人,阿七一開始在 \((0,0)\),每次可以向上或者向右走一格,如果當前在 \((a,b)\) 那麼給 \((x_i,y_i)\) 的人剪頭髮的代價是 \(\max(|x_i-a|,|y_i-b|)\),最強髮型師阿七想給每個人都剪一次頭髮,請你求出最小的代價。

\(1\leq n\leq 8\cdot 10^5,0\leq x_i,y_i\leq 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)\) 並且給所有 \(x\leq a\) 的人剪頭髮的最小花費,轉移:

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

但是這樣就穩 \(T\) 了,觀察轉移可以發現其實有兩種操作:和某一個絕對值函式合併;範圍取 \(\min\) 更新單點;可以考慮用 \(\tt slope\space trick\) 的方法,把 \(dp[a]\) 當成一個折線函式來維護。

不難發現 \(0\) 斜率的折線是最優解存在的地方,那麼可以把左右邊分開,左邊斜率 \(<0\),右邊斜率大於 \(>0\)在範圍取 \(\min\) 更新單點的時候兩邊的函式值都會向中間收攏,轉折點會根據 \(0\) 斜率線發散,這和 \(a\) 的差值是有關的,所以我們可以打標記,左邊的轉折點在插入的時候加 \(a\),在取出轉折點的時候減 \(a\),右邊加減對換即可,搞兩個優先佇列 \(L,R\) 維護轉折點。

現在解決了第二個問題,第一個問題要考慮斜率的變化,這對優先佇列裡的元素有影響,分情況討論:

  • 如果 \(0\) 斜率線的左端點 \(>y\),單個人 \(y\) 處斜率會由 \(-1\)\(1\),這個可以直接合並,所以把 \(y\) 插進 \(l\) 兩次即可,但這會導致左端點(也就是 \(L\) 中最大的點)不再屬於 \(L\),這時候要把它彈掉並且插入到 \(R\) 中去。
  • 如果 \(0\) 斜率線的右端點 \(<y\),右端點是 \(R\) 中最小的點,類似情況一操作即可。
  • 如果 \(y\)\(0\) 斜率線上,那麼在 \(L,R\) 中都插入 \(y\) 來更新 \(0\) 斜率線。

在上述操作中維護 \(0\) 斜率線的函式值,時間複雜度 \(O(n\log n)\)

#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\n",ans/2);
}