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,那麼結果肯定是<=(向上取整),算出來最多25個二進制項。那麼我們直接計算答案轉換爲二進制的每一項,最後合併成10進制的答案就行了。
- 那麼問題來了,如何知道答案的第i位是0還是1呢?我們可以直接找a數組中兩項和的i位是1的項有多少個。奇數個,答案的第i位是1,否則是0.
- 現在問題就是如何在a數組中尋找有多少個兩項之和的第i位爲1。
- 假設現在在計算答案的第k位。我們可以直接枚舉a數組的每一項,然後再到後面尋找使得的第k位是1.這個尋找我們可以想到二分查找。現在我們需要把k位以上的都先抹除掉,不然不好找。
- 假設我們在考慮k=4,那麼的範圍都在,所以的範圍:,現在需要他倆的和第k位是1,那麼符合要求的範圍就成了:。現在把的範圍用含k的式子表示,那麼就是:
- 所以現在可以直接枚舉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節點,所以而。所以以{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;
}