Codeforces Round #626 (ABCDE)


Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)


前言


比賽AC


A. Even Subset Sum Problem

簡明題意

  • 給定長度爲n的數組a。需要你輸出a的一個子序列,使這個子序列的和是偶數

正文

  • 判斷a中奇數和偶數的個數,如果奇數數量>=2,則任意輸出兩個奇數。如果偶數數量>=1,則任意輸出一個偶數。否則輸出-1

代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;

const int maxn = 1e5 + 10;
int a[maxn];
int b[maxn];

void solve()
{

	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;

		int ji = 0, ou = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			if (a[i] % 2 == 0) ou++, b[i] = 1;
			else ji++, b[i] = 0;
		}

		if (ou >= 1 || ji >= 2)
		{
			if (ou >= 1)
			{
				cout << 1 << endl;
				for (int i = 1; i <= n; i++)
					if (b[i] == 1)
					{
						cout << i << endl;
						break;
					}
			}else
			{
				cout << 2 << endl;
				int cnt = 0;
				for (int i = 1; i <= n; i++)
					if (b[i] == 0)
					{
						cout << i << " ";
						cnt++;
						if (cnt == 2)
							break;
					}
			}
		}
		else
		{
			cout << -1 << endl;
		}
	}
}

int main()
{
	//freopen("Testin.txt", "r", stdin);
	solve();
	return 0;
}

B. Count Subrectangles

簡明題意

  • 給長度爲n的01串a和長度爲m的01串b,現在有一個n*m的矩陣,矩陣中的每一個元素g[i][j]=a[i]*b[j]。現在假設g矩陣計算出來了,詢問面積爲k的全1矩形有多少少個(這個矩形裏每個元素必須是1)

正文

  • 假設a[3]=1,那麼,如果b[2]=1,b[3]=1,我們就會發現有一個12的全1矩形.如果a[2]=a[3]=1,b[3]=b[4]=b[5]=1,那麼就是23的全1矩形.
  • 所以我們可以枚舉a中的連續1的數量,乘以b中連續1的數量,就是答案。
  • 所以對k質因數分解,假設質因子是p,那麼一條邊長爲p,另一條爲k/p,我們只需要在a中尋找連續長度爲p的數量,乘以b中連續長度爲k/p的數量,累乘起來就行。
  • 問題在於怎麼計算數組中連續某個長度的數量。這個可以把數組掃一遍,找到每一段連續的1的長度。比如我找到了一段連續5個1,假設數組rec[i]記錄連續i個1的數量,那麼rec[1]+=5,rec[2]+=4,rec[3]+=3,rec[4]+=2,rec[5]+=1.這樣好像並不是很好統計。我當時是這樣想的,假設當前已經5個1了,再增加一個1,你會發現,rec[1],rec[2],rec[3],rec[4],rec[5],rec[6]都會增加1,所以這相當於區間加和,用差分維護一下就可以了
  • 要注意最後質因數分解k的時候,假設k很大而n,m很小,那麼p可能同時大於nm,這時候你去計算rec[p]就會導致re。所以每次質因數分解了,要特別判斷一下p、k/p的合法性。

代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;

const int maxn = 1e5 + 10;


int cf[40000 + 10];


int rec_aa[40000 + 10];
int rec_bb[40000 + 10];

void solve()
{
	int n, m, k;
	cin >> n >> m >> k;
	int cur = 0;
	for (int i = 1; i <= n; i++)
	{
		int t; scanf("%d", &t);
		if (t == 1)
		{
			cur++;

			cf[cur + 1]--;
			cf[1]++;
		}
		else if (t == 0)
		{
			cur = 0;
		}
	}
	for (int i = 1; i <= n; i++)
		rec_aa[i] = rec_aa[i - 1] + cf[i];

	memset(cf, 0, sizeof cf);
	cur = 0;
	for (int i = 1; i <= m; i++)
	{
		int t; scanf("%d", &t);
		if (t == 1)
		{
			cur++;

			cf[cur + 1]--;
			cf[1]++;
		}
		else if (t == 0)
		{
			cur = 0;
		}
	}
	for (int i = 1; i <= m; i++)
		rec_bb[i] = rec_bb[i - 1] + cf[i];

	long long ans = 0;
	for (int i = 1; i * i <= k; i++)
		if (k % i == 0)
		{
			if (i <= n && k / i <= m)
				ans += rec_aa[i] * rec_bb[k / i];
			if (i *i != k && k / i <= n && i <= m)
				ans += rec_aa[k / i] * rec_bb[i];
		}

	cout << ans;
}

int main()
{
	//freopen("Testin.txt", "r", stdin);
	solve();
	return 0;
}

C. Unusual Competitions

簡明題意

  • 給定n長的括號序列,每次能將一串長度爲l的連續子串重排,代價爲l。現在想要把括號序列變得合法,問最小的代價。

正文

  • 我的思路是,把(當成1,)當成-1,然後順序考慮這個字符串。
  • 每次從遇到的第一個-1開始,假設這個位置爲x,直到累加後和爲0,假設這個位置爲y,那麼,y-x+1就是這一次的消耗。直到遇到下一個-1.
  • 這樣做爲啥是對的呢?其實我也不太懂,反正當時就搞出來這個想法,沒想到就A了。。。

代碼

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<cstdio>	
#include<string>
using namespace std;

const int maxn = 1e6 + 10;

char a[maxn];

void solve()
{
	int n; cin >> n;
	scanf("%s", a + 1);

	int ans = 0, cur = 0, st = -1;
	for (int i = 1; i <= n; i++)
	{
		if (a[i] == '(') cur++;
		else if (a[i] == ')') cur--;
		if (cur == 0 && st != -1) ans += i - st + 1, st = -1;
		if (cur < 0 && st == -1) st = i;
	}
	cout << (cur == 0 ? ans : -1);
}

int main()
{
	//freopen("Testin.txt", "r", stdin);
	solve();
	return 0;
}

賽後補題


D. Present

簡明題意

  • 給定長度爲n(2<=n<=4e5)的數組a,求兩兩之和的異或值。

正文

  • 考慮到異或項最大是2e7,那麼結果肯定是<=2[log2(2e7)]2^{[log_2(2e7)]}(向上取整),算出來最多25個二進制項。那麼我們直接計算答案轉換爲二進制的每一項,最後合併成10進制的答案就行了。
  • 那麼問題來了,如何知道答案的第i位是0還是1呢?我們可以直接找a數組中兩項和的i位是1的項有多少個。奇數個,答案的第i位是1,否則是0.
  • 現在問題就是如何在a數組中尋找有多少個兩項之和的第i位爲1。
  • 假設現在在計算答案的第k位。我們可以直接枚舉a數組的每一項aia_i,然後再到aia_i後面尋找aja_j使得ai+aja_i+a_j的第k位是1.這個尋找我們可以想到二分查找。現在我們需要把k位以上的都先抹除掉,不然不好找。
  • 假設我們在考慮k=4,那麼aiaja_i和a_j的範圍都在[0,11111][0,11111],所以ai+aja_i+a_j的範圍:[0,11111+11111]=[0,111110][0,11111+11111]=[0,111110],現在需要他倆的和第k位是1,那麼符合要求的ai+aja_i+a_j範圍就成了:[10000,11111][110000,111110][10000,11111]\cup[110000,111110]。現在把ai+aja_i+a_j的範圍用含k的式子表示,那麼就是:[2k,2k+11][2k+1+2k,2k+22][2^k,2^{k+1}-1]\cup[2^{k+1}+2^k,2^{k+2}-2]
  • 所以現在可以直接枚舉a[i]的每一項,然後二分查找在剛剛求出的那個區間的數有多少個,如果是奇數個,那麼第k位是1,偶數個則是0.

代碼

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<cstdio>	
#include<map>
#include<string>
using namespace std;

const int maxn = 4e5 + 10;

int a[maxn], b[maxn];

void solve()
{
	int n; cin >> n;
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);

	int ans = 0;
	for (int k = 0; k <= 26; k++)
	{
		for (int i = 1; i <= n; i++)
			b[i] = a[i] % (1 << (k + 1));
		sort(b + 1, b + 1 + n);

		long long cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			int p1 = lower_bound(b + 1 + i, b + 1 + n, (1 << k) - b[i]) - b;
			int p2 = upper_bound(b + 1 + i, b + 1 + n, (1 << (k + 1)) - 1 - b[i]) - b - 1;
			cnt += 1ll * p2 - p1 + 1;

			p1 = lower_bound(b + 1 + i, b + 1 + n, (1 << (k + 1)) + (1 << k) - b[i]) - b;
			p2 = upper_bound(b + 1 + i, b + 1 + n, (1 << (k + 2)) - 2 - b[i]) - b - 1;
			cnt += 1ll * p2 - p1 + 1;
		}
		ans += (cnt & 1) * (1 << k);
	}
	cout << ans;
}

int main()
{
	//freopen("testin.txt", "r", stdin);
	solve();
	return 0;
}


E. Instant Noodles

簡明題意

  • 有一個二分圖,左右的頂點數都是n。再給出m條邊。右端的每個點有一個權值a[i]
  • 現在定義s是左端點的一個子集,這個集合s的值f(s),定義爲二分圖右端所有與s有連邊的點的權值和。
  • 現在求所有的f(s)互相的gcd

正文

  • 看到題目的問題,子集都gcd起來,這樣我們應該聯想到gcd的一個性質,gcd(a,b,c)=gcd(a,b,c,a+b,a+c,b+c,a+b+c)。這時候,我們把a,b,c稱爲最小單位,也就是無論gcd式子裏是多少個最小單位的和,其結果都是最小單位的gcd。因此只需要考慮最小單位的gcd
  • 也就是說,比如左點是{1,2,3},那麼所形成的所有集合有{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}。(空集由於題意不計)。所以把這個對應到上面的gcd式子,我們可以發現是不是直接計算{1},{2},{3}的gcd就可以了呢?因爲{1},{2}這樣的集合是最小單位。
  • 差不多是這個意思,但是這樣不對。因爲這個s到f(s)是有兩層映射的。也就是說,f({1})+f({2})!=f({1,2}),因爲,假設1號節點映射到了右端的1,2節點,而2號節點映射到1,3節點,所以f(1)=c1+c2,f(2)=c1+c3f(1)=c_1+c_2,f(2)=c_1+c_3f(1,2)=c1+c2+c3!=f(1)+f(2)=c1+2c2+c3f(1,2)=c_1+c_2+c_3 !=f(1)+f(2)=c_1+2c_2+c_3。所以以{1},{2}…這樣的集合爲最小單位是不對的。
  • 考慮到每一個f(s)都是右端一些點的組合。那麼我們是不是可以直接以右端的每一個點爲最小單位呢?這樣看起來很合理,但實際上也不對。看這樣一個例子,共有123三個點,1映射到12,2映射到12,3映射到1。這時我們發現N(s)中根本沒有出現1,2這樣單獨的點,所以直接以右端點爲基本單位也是不對的。但此時,我們可以發現,直接以1,2整體作爲基本單位,就是對的了。所以最終就是,所有的在右端的,具有相同邊的點是基本單位,把這些點的權值和gcd起來就是答案。
  • 總結一下,以後遇到很多數的gcd,我們可以考慮找到gcd的基本單位。

代碼

#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<cstdio>	
#include<map>
#include<string>
using namespace std;

const int maxn = 5e5 + 10;

long long gcd(long long a, long long b)
{
	if (b == 0) return a;
	return gcd(b, a % b);
}

long long a[maxn];
vector<int> g[maxn];
map<vector<int>, long long> rec;

void solve()
{
	int t;
	cin >> t;
	while (t--)
	{
		int n, m;
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++)
			scanf("%lld", &a[i]), g[i].clear();

		rec.clear();
		
		while (m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[v].push_back(u);
		}

		for (int i = 1; i <= n; i++)
		{
			if (g[i].size())
			{
				sort(g[i].begin(), g[i].end());
				rec[g[i]] += a[i];
			}
			
		}

		long long ans = -1;
		for (auto& it : rec)
			if (ans == -1) ans = it.second;
			else ans = gcd(ans, it.second);

		printf("%lld\n", ans);
	}

}

int main()
{
	//freopen("Testin.txt", "r", stdin);
	solve();
	return 0;
}

簡明題意

正文

代碼


總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章