Codeforces Round #699 (Div. 2) A~E 題解


本場鏈接:Codeforces Round #699 (Div. 2)

A. Space Navigation

題目大意:有一個飛船一開始在\((0,0)\),有一個操作列表\(s\),每個位置表示一個上下左右的移動操作.有一個目的地坐標是\((px,py)\).但是這個操作序列不一定能夠正確的走到目的地,現在問是否能通過刪去某些操作的方式使得飛船能走到目的地.只需輸出是否.

思路

刪除任意元素且不關注刪除的數量和具體方案.那么只需考慮是否能通過單一的操作走到最后的目的地就可以了.

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
char s[N];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int px,py;scanf("%d%d",&px,&py);
		scanf("%s",s + 1);int n = strlen(s + 1);
		vector<int> cnt(4,0);
		forn(i,1,n)
		{
			if(s[i] == 'U')	++cnt[0];
			else if(s[i] == 'D')	++cnt[1];
			else if(s[i] == 'L')	++cnt[2];
			else ++cnt[3];
		}
		
		bool ok = 1;
		if(px >= 0 && px > cnt[3])	ok = 0;
		if(px < 0 && -px > cnt[2])	ok = 0;
		if(py >= 0 && py > cnt[0])	ok = 0;
		if(py < 0 && -py > cnt[1])	ok = 0;
		if(!ok)	puts("NO");
		else puts("YES");
	}
	return 0;
}

B. New Colony

題目大意:有\(n\)個山,每個山有高度\(h_i\).人站在第一個山頭上丟牛逼土,牛逼土有自動檢查當前高度的牛逼功能,具體來說:

  • 如果當前的位置是\(i\),那么如果\(h_i \geq h_{i+1}\)那么牛逼土會直接飛到下一個山頭.
  • 反之牛逼土會覺得當前這個山頭不夠牛逼,並且犧牲自己讓當前的山頭高度增加一,即\(h_i+=1\).
  • 特殊情況:牛逼土可能會飛出\(n\)個山頭之外.

一共有\(k\)個牛逼土,每次都在\(1\)這個位置丟,

思路

這個題如果上手模擬一下就會發現情況其實很復雜,因為當前的山頭即使走到了下一個山頭,那么下一個山頭如果還是需要填充高度的話,則仍然會重新走到第一個山頭,也就是說牛逼土落到山頭的位置是不單調的,雖然有規律但是也很復雜.

但是這就啟發了一個事情:這個題找規律是死路一條,結合數據范圍不難猜到極大的情況也不會太多,所以直接模擬沖一發.當然最后就過了.

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e6+7;
int ans[N],h[105];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n,k;scanf("%d%d",&n,&k);
		forn(i,1,n)	scanf("%d",&h[i]);
		h[n + 1] = 0;
		int last = 1;
		bool ok = 0;
		while(1)
		{
			forn(i,1,n)
			{
				if(i == n)	{ans[last++] = -1;ok = 1;}
				if(h[i] >= h[i + 1])	continue;
				++h[i];
				ans[last++] = i;
				if(last > k)	ok = 1;
				break;
			}
			if(ok)	break;
		}
		if(k < last && ans[k])	printf("%d\n",ans[k]);
		else puts("-1");
	}
	return 0;
}

C. Fence Painting

題目大意:有\(n\)個柵欄,每個柵欄一開始有一個顏色\(a_i\),但是你覺得這個柵欄不夠牛逼,你想每個柵欄最后的顏色是\(b_i\)才是牛逼的.但是你也不會塗柵欄,所以現在有\(m\)個粉刷匠,每個人順次來,每個人能塗的顏色是\(c_i\),並且每個人由於提前給過錢了,所以每個人來的時候必須要塗一個柵欄,不能不塗.現在問是否在所有人塗完了之后,能讓每個柵欄的顏色達到目標,如果是的話,則輸出一個構造的方案,否則輸出不能.

思路

這個題的模型可以抽象一下:就是有若干個操作順次執行,每個操作類似於覆蓋區間/單點的值,問你最后能不能讓整個序列達到什么目標.這個模型有一個很獨立的特點:正向的考慮每個覆蓋操作會比較麻煩,因為你當前不知道后面的覆蓋操作會有什么誤差,而且也不可能直接去遍歷所有操作看看能不能讓以后的操作給你開個后門,白話一下就是你必須大概是個線性(或者帶個\(\log\))的做法,但是如果順次考慮就很困難.不過反過來考慮就很輕松.

反過來做所有人,那么假如說當前某個人可以畫到一個合適的位置,之后往前如果某個人找不到一個合理的位置去塗色,那么可以直接讓這個人畫到相對於位置之后的那個人的位置,這步策略的意思就是如果一個人沒有一個合理的位置去填色的話,那么就填到以后一個會填好的位置,因為塗色的順序是從前往后的,順次來看,讓這個人塗色的位置會被更后面的一個人覆蓋上正確的顏色,那么就不會對正確性產生影響了.

策略確定好了之后可以考慮怎么構造了:首先看一下所有的柵欄的顏色是否是對應的,假設如果不是對應的,那么對於\(b_i\)這個顏色,必須要有一個能畫\(b_i\)這個顏色的人去畫\(i\)這個位置,所以開一個鄰接表儲存所有不匹配的\(b_i\)需要畫在的位置的編號.反過來如果確實是對應的,那么對應的有個好處:\(b_i\)顏色的人如果過來處理發現沒有一個需要他的位置可以直接畫在一個本來就是這個顏色的位置,不產生影響,這樣的也是合法的.

最后處理所有人,並且對\(a_i\)真實的修改,假如某個人的顏色已經沒有位置給他填補了,而且也不存在一個本來就是這個顏色的位置,就直接輸出無解,反之就有哪填哪.記錄下對應的位置最后輸出就可以了.

這個題細節還是很多的,還是建議自己把整個題做出來,完整的考慮清楚每一步.

代碼

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
#define forr(i,x,n)	for(int i = n;i >= x;--i)

const int N = 1e5+7;
int a[N],b[N],c[N],ans[N];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n,m;scanf("%d%d",&n,&m);
		forn(i,1,m)	ans[i] = 0;
		forn(i,1,n)	scanf("%d",&a[i]);
		forn(i,1,n)	scanf("%d",&b[i]);
		forn(i,1,m)	scanf("%d",&c[i]);
		
		map<int,vector<int>> need;
		map<int,int> backup;
		forn(i,1,n)
		{
			if(a[i] == b[i])
			{
				backup[b[i]] = i;
				continue;
			}
			if(!need.count(b[i]))	need[b[i]] = vector<int>();
			need[b[i]].push_back(i);
		}
		
		vector<int> bad;
		int last_ok = -1,ok = 1;
		
		forr(i,1,m)
		{
			if(!need.count(c[i]))
			{
				if(last_ok == -1 && !backup.count(c[i]))
				{
					ok = 0;
					break;
				}
				if(last_ok != -1)	ans[i] = ans[last_ok],a[ans[i]] = a[ans[last_ok]];
				else 
				{
					ans[i] = backup[c[i]];
					last_ok = i;
				}
				continue;
			}
			ans[i] = need[c[i]].back();
			a[ans[i]] = c[i];
			need[c[i]].pop_back();
			if(need[c[i]].empty())	need.erase(c[i]);
			if(last_ok == -1)	last_ok = i;
		}
		forn(i,1,n)	if(a[i] != b[i])	ok = 0;
		
		if(!ok)	puts("NO");
		else
		{
			puts("YES");
			forn(i,1,m)	printf("%d ",ans[i]);puts("");
		}
	}
	return 0;
}

D. AB Graph

題目大意:給定一個\(n\)點的完全圖,每個邊有一個字母ab.注意:兩個點之間的兩條邊上的字母是不一定相同的.另外給定一個數字\(m\)要求你構造一個長為\(m\)的路徑,並且這條路徑上\(m\)條邊上的字母連起來需要是一個回文串.

思路

來討論一波就可以了,沒那么復雜.

首先可以很容易注意到的一個特殊情況就是如果兩個點之間兩條邊上的字母是相同的,那么可以構造任意情況,來回跑就可以了.

那么接下來,刪掉所有邊相同的情況,剩下的只有兩個點之間邊元素不相同的.對於這種情況通過模擬可以找到:只用這樣的兩個點,可以構造出所有\(m\)是奇數的情況.方式就是繞圈走就可以了.

剩下還有最后一種:所有兩個點之間邊都是不同的,並且\(m\)還是個偶數.由於這個題目數據范圍比較大,可以猜一下就是正確的構造方式,它包含的點數肯定不多,然后可以找一下三個點之間的關系,可以發現是只有這樣的三元組:\((u,v,z)\)之間存在路徑\(u->v->z\)且兩個邊的元素相同,這時可以構造所有\(m\)是偶數的情況,如果不存在的話就無解.然后繼續討論可以發現兩種情況:如果\(m\)是四的倍數,這樣的情況可以從\(v\)出發先往\(z\)繞一圈再往\(u\)繞一圈回\(v\).反之就從\(u\)出發走到\(z\)再繞回\(u\).兩種情況分別去構造就可以了.

這題也是細節多,需要考慮清楚輸出的內容.

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
typedef pair<int,int> pii;
#define x first
#define y second

const int N = 1005;
char s[N][N];
int have[N][2];

int main() 
{
	int T;scanf("%d",&T);
	while(T--)
	{
		int n,m;scanf("%d%d",&n,&m);
		forn(i,1,n)	scanf("%s",s[i] + 1),have[i][0] = have[i][1] = -1;
		
		bool same = 0;pii res = {-1,-1};
		forn(u,1,n)	
		{
			forn(v,1,n)
			{
				if(u == v)	continue;
				if(s[u][v] == s[v][u])
				{
					same = 1;
					res.x = u,res.y = v;
					break;
				}
			}
			if(same)	break;
		}
		
		if(same)
		{
			puts("YES");
			for(int i = 1;i <= m + 1;i ++)
			{
				if(i % 2 == 1)	printf("%d ",res.x);
				else printf("%d ",res.y);
			}
			puts("");
			continue;
		}
		
		if(m & 1)
		{
			puts("YES");
			for(int i = 1;i <= m;i += 2)
			{
				printf("1 2 ");
			}
			puts("");
			continue;
		}
		
		bool ok = 0;
		forn(u,1,n)	forn(v,1,n)
		{
			if(u == v)	continue;
			have[u][s[u][v] - 'a'] = v;
		}
		
		forn(u,1,n)
		{
			forn(v,1,n)
			{
				if(u == v || have[v][s[u][v] - 'a'] == -1)	continue;
				int z = have[v][s[u][v] - 'a'];
				puts("YES");
				vector<int> cire = {v,z,v,u},ciro = {u,v,z,v};
				int p = 1;
				if((m / 2) % 2 == 0)
				{
					printf("%d ",v);
					forn(i,1,m)
					{
						printf("%d ",cire[p++]);
						if(p == 4)	p = 0;
					}
				}
				else
				{
					printf("%d ",u);
					forn(i,1,m)
					{
						printf("%d ",ciro[p++]);
						if(p == 4)	p = 0;
					}
				}
				puts("");
				ok = 1;
				break;
			}
			if(ok)	break;
		}
		if(!ok)	puts("NO");
	}
	return 0;
}

E. Sorting Books

題目大意:有\(n\)本書,每本書有自己的顏色.給定一個操作:把一本書移動到所有書的末尾.求最少操作多少次可以讓所有相同顏色的書放在一起.

思路

這個題的idea是這題1367F2 flyingsort的弱化.這個轉移還是比較獨特的,沒見過可能做不出來.

可以發現這個題的要點是對每本書操作怎么做,也就對應決策這件事,可以想到用dp來做每一步的決策,實際移動肯定是不可能的,那么順序的考慮dp會很復雜,因為往后的時候還需要考慮前面有哪些元素移動到了末尾,這件事情很麻煩,所以一個現在的方向是從后往前dp.

問題就是dp看的是什么.如果去考慮每個元素要不要移動到末尾道理是講不通的,因為你不知道這個顏色對應的整體的形式是長什么樣的,那么另外一個關鍵點就是這個dp他想要求的東西必須要直接或者間接地看出來每個顏色的書具體在序列里面是不是合理的,他們分布是什么樣的.順着這個思路可以考慮:\(f[i]\)表示把從\(i\)開始的后綴里面所有的顏色都合並到一起的代價,但是這個也做不了,因為無從得知這個后綴里面的顏色如果還存在相同顏色且不在這個后綴里面,后續拼接上的代價.但是不妨把這個狀態表示反轉一下:

  • 狀態:\(f[i]\)表示對於\(i\)開始的后綴,里面最多有多少個元素不需要移動
  • 入口:全\(0\)即可
  • 轉移:在轉移的時候,我們其實只需要考慮上一個狀態\(f[i+1]\)與之有什么不同:也就是加入了一個\(a_i\).那么假如\(i\)這個位置是\(a_i\)這個顏色第一次出現的位置,那么轉移的重點只在於:保留\(a_i\)都不變化和不保留\(a_i\)的操作區別,分別對應\(f[i] = cnt[a[i]] + f[R[a[i]] +1]\)以及\(f[i] = f[i + 1]\),其中這個\(R[a[i]]\)表示\(a[i]\)這個顏色最后的位置,\(cnt[a[i]]\)表示的是在整個序列里面某個顏色出現的次數(后一個轉移看形式其實也看得出來,他實際上對應的就是什么事都不干的轉移方式,后續討論的時候就不再提了).接下來考慮,那如果\(a_i\)又不是第一次出現的,他之前還有,怎么轉移?是不是就跟上面第一個粗糙的想法里提到的轉移方程也是一樣不能處理后續的情況的呢?事實上不是,這個轉移方程好就好在他可以接上之后的轉移,因為他只關注序列里面有多少個,而不關注他的具體位置.這個情況的轉移就是\(f[i] = cur\_cnt[a[i]]\),其中\(cur\_cnt\)表示的意思是當前這個后綴里面顏色的個數,而不是整個序列里面的個數.
  • 出口:\(res = n - f[1]\)

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
#define forr(i,x,n)	for(int i = n;i >= x;--i)

const int N = 5e5+7;
int f[N],cnt[N];
int L[N],R[N],a[N];

int main() 
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%d",&a[i]);
	
	forn(i,1,n)
	{
		if(!L[a[i]])	L[a[i]] = i;
		R[a[i]] = i;
	}
	
	forr(i,1,n)
	{
		++cnt[a[i]];
		if(i == L[a[i]])	f[i] = max(f[i],cnt[a[i]] + f[R[a[i]] + 1]);
		else 	f[i] = max(f[i],cnt[a[i]]);
		
		f[i] = max(f[i],f[i + 1]);
	}
	
	printf("%d",n - f[1]);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM