1. 程式人生 > 實用技巧 >CF1270G Subset with Zero Sum

CF1270G Subset with Zero Sum

首先一定要從每個數的範圍 \(i - n \le a_i \le i - 1\) 入手,最開始是這樣一個想法,不難發現對於每個 \(i\) 都能選 \(n\) 個數,並且能選的右端點在 \(i - 1\),那麼我們可以吧每個 \(i\) 前移一位,實際上就是 \(0 \sim n - 1\) 這些位置上選沒選數,而如果這個位置上沒選那麼就一定選在了前面,實際上這樣還是非常不好做,於是我考慮只選擇了一個負數的情況,發現還是不能發現一種簡便的方法來找出這個集合,因此我們需要換一種方式思考。

既然分析性質行不通,我們可以考慮直接構造出答案。像這種找到一個集合滿足一些條件的題目,我們可以將其轉化為圖論問題,例如求一個連通塊的權值和為特定值或者找到一個環使得環上權值和為特定值,轉化方式就需要巧妙的連邊了。因為 \(a_i\)

涉及到負數不方便連邊,於是我們可以對於每個 \(a_i\) 增添一個增量 \(k\),即將 \(a_i \rightarrow b_i = a_i + k_i\) 那麼原問題就轉化為尋找一個集合使得 \(\sum b_i = \sum k_i\)。我的想法是對於每個 \(i\) 我們連邊 \(i \rightarrow a_i + k_i\) 可以發現這中間有 \(n\) 條邊,因為是有向邊我們找聯通塊不方便,因此可以考慮找環。於是我們接下來的目的就是尋找一個合適的 \(k_i\) 使得連出來環上的邊滿足上述條件,然後我就不會做了,下面的做法只能無限 \(\rm stO\) 出題人。首先我們需要注意到這樣一件事,如果剛好 \(n\)
個點,\(n\) 條邊那麼圖中一定會存在一個環,於是我們就可以讓 \(1 \le a_i + k_i \le n\) 那麼這樣就一定會出現環了,與此同時我們需要注意到一個如果要滿足 \(\sum b_i = \sum k_i\) 那麼不難發現環上的權值和要是編號和,事實上根據之前的連邊如果能出現環那麼環上的權值和一定是編號和,因為我們是每個編號 \(i\) 向外連一條邊。再觀察一下資料範圍 \(i - n \le a_i \le i - 1\),兩邊不正好出現了 \(1, n\) 嗎?先把兩邊減去 \(i\) 可得 \(-n \le a_i - i \le -1\) 再反個號 \(1 \le i - a_i \le n\)
不就是我們想要的東西嗎?於是對於每個 \(i\) 我們連邊 \(i \rightarrow i - a_i\) 再在圖上找一個環即可。注意多組資料不能直接 \(\rm memset\)

#include<bits/stdc++.h>
using namespace std;
#define N 1000000 + 5
#define rep(i, l, r) for(int i = l; i <= r; ++i)
bool book[N];
int T, n, x, top, cnt, a[N], fa[N], st[N], ans[N];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int main(){
    T = read();
    while(T--){
        n = read();
        rep(i, 1, n) a[i] = read(), fa[i] = i - a[i];
        x = 1, cnt = top = 0;
        while(!book[x]) book[x] = true, st[++top] = x, x = fa[x];
        while(st[top] != x) book[st[top]] = false, ans[++cnt] = st[top--];
        ans[++cnt] = st[top];
        while(top) book[st[top--]] = false;
        printf("%d\n", cnt);
        rep(i, 1, cnt) printf("%d ", ans[i]); puts("");
    }
    return 0;
}