Bitset模板 Bitset題型大薈萃
阿新 • • 發佈:2018-12-23
以codeforces上的ASC28J為例,講了一些我遇到的Bitset的題目及做法
#include<stdio.h> #include<iostream> #include<string.h> #include<ctype.h> #include<math.h> #include<map> #include<set> #include<vector> #include<queue> #include<functional> #include<string> #include<algorithm> #include<time.h> #include<bitset> void fre(){freopen("triatrip.in","r",stdin);freopen("triatrip.out","w",stdout);} template <class T> inline void scand(T &x){char c;x=0;while((c=getchar())<'0');while(c>='0'&&c<='9')x=x*10+(c-48),c=getchar();} #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned int UI; template <class T> inline void gmax(T &a,T b){if(b>a)a=b;} template <class T> inline void gmin(T &a,T b){if(b<a)a=b;} using namespace std; const int N=1515,M=0,Z=1e9+7,maxint=2147483647,ms31=522133279,ms63=1061109567,ms127=2139062143; int n,i,j; char s[N]; /* 【演算法介紹】 bitset是可以類似於狀壓DP,可以對01狀態進行壓縮和表示。 <1>bitset不僅限於32bits or 64bits,而是可以達到甚至是1e8 bits <2>bitset支援or and xor << >> 等位運算,效率很高 其常用函式如下: b.any() 判斷b中是否存在值為1的二進位制位 b.none() 判斷b中是否不存在值為1的二進位制位 b.count() 判斷b中值為1的二進位制位個數 b.size() 判斷b中二進位制位的個數 b[pos] 訪問b中在pos處的二進位制位 b.test(pos) 判斷b中在pos處的二進位制位是否為1 b.set() 把b中所有二進位制位都置為1 b.set(pos) 把b[pos]置為1 b.reset() 把b中所有二進位制位都置為0 b.reset(pos) 把b[pos]置為0 b.flip() 把b中所有二進位制位逐位取反 b.flip(pos) 把b[pos]取反 scanf("%lld", &e); cout << e << endl; //對bitset做讀入或輸出,輸出的位置關係為高位 -> 低位 e[pos] = 1; cout << e << endl; //pos的位置關係在bitset上,相對位置關係為高位 -> 低位 使得e <<= 1相當於對於所有的x,都有e[x] = e[x - 1] UI* v = (UI*)&T; //把bitset轉化為UI的陣列 使用舉例1 ASC28J:------------------------------------ http://codeforces.com/gym/100342/attachments/download/2145/20072008-winter-petrozavodsk-camp-andrew-stankevich-contest-28-asc-28-en.pdf [題意] 給你一個可達1500大小的01矩陣,b[i][j]==1表示從i到j有邊。 問你這個圖中含有多少個三元環。 [分析] 最暴力的O(n^3)做法是—— for(i=0;i<n;i++)for(j=0;j<n;j++)for(k=0;k<n;k++) if(a[i][j]&&a[j][k]&&a[k][i])ans++; 但這個時間複雜度是O(n^3),於是想到用bitset優化 如果有(j->i)以及(i->k)邊和(k->j)邊,那麼就構成了一個三元環。 於是for(i=0;i<n;i++)for(j=0;j<n;j++)if(out[j][i]) ans+=(out[i]&in[j]).count(); (i->0)(i->1)(i->2)...(i->n) (0->j)(1->j)(2->j)...(n->j) 兩者&一下,然後再count()一下。就AC啦 使用舉例2 TOJ4119:------------------------------------ 1個bool是1位元組,8個bitset單位才是1位元組。 所以,用bool可以實現的,幾乎都可以用bitset優化時間複雜度+空間複雜度 使用舉例3 HDU5313 or GYM100781F ---------------------- 注意,bitset可以說b[0]是最低位, 左移<<=會使得其變大(最左邊補0) 右移>>=會使得其變小(最右邊補0) 如果我們在做一個DP, 對於普遍性操作f[i]|=f[i-x],可以寫成f|=f<<x 對於普遍性操作f[i]=f[i-x],可以寫成f<<=x 對於普遍性操作f[i]|=f[i+x],可以寫成f|=f>>x 對於普遍性操作f[i]=f[i+x],可以寫成f>>=x 如果存在溢位,是會用0來補的。但是小心邊界問題。 比如我們定理了一個長度為8的bitset,卻只需要用到長度為4的。 那麼f<<=x的時候可能使得4 5 6 7位置也可能為1,然後後來再>>=,可能達不到自然溢位的效果,會出錯。 補救的做法可以是設定特殊bitset b,數值為1的大小等於其所需要bitset尺寸,其它位置數值為0 然後每次操作之後&b,就相當於消除溢位,保證正確性 使用舉例4 弱校聯萌1001D:------------------------------------ http://acm.hdu.edu.cn/webcontest/contest_login.php?cid=7686 [題意] 給你一個圖,n(500)個點,m(C(n,2))條邊 每條邊都至少有一個點的編號是<=30的, 讓你對一些節點染色,使得每條邊都至少有一個點是染色的,問你最少染色的節點數。 [解法] 正解是bitset爆搜。 我們只需要列舉<=min(n,30)個點的選取狀態。 如果與一個點是選取的,那麼,所有與這些點相連的邊的另外一個點(>30)都可以不用選取。 如果一個點是沒有選取的,為了覆蓋邊,所有與這個點相連的邊的另外一個點都必須選取。 e[x]表示x這個點的選取情況,a[x][y]表示x與y之間有邊,其中x是限定在[0,29]範圍內的。 然後從0到29開始搜,依次列舉一個點選或不選。 選的話e[x]標記為1 有e|=a[x],程式碼如下: void dfs(int p,bitset<500>e) { int num=e.count(); if(num>=ans)return; if(p==top) { ans=num; return; } if(e[p])dfs(p+1,e);//這個點已經被染色,與之有邊的點都可以不染色 else//這個點未被染色 { e[p]=1;dfs(p+1,e);//這個點染色,與之有邊的點都可以不染色 e[p]=0;dfs(p+1,e|a[p]);//這個點不染色,與之有邊的點必須染色 } } 使用舉例5 CAMP原題,打球進洞:------------------------------------ [題意] 有n(5000)個集合,每個集合有m(50範圍內)個數(數字範圍也在50範圍內) 第一個集合的(所有位置)都有一個球 第二個集合的(所有位置+0.5)都有一個洞 我們列舉2個集合pair,共C(n,2)種,第一個集合作為球,第二個集合作為洞。 我們將所有的球座標變大,進入它們遇到的第一個洞裡。 如果有奇數個洞至少有一個球,就獲勝。 問對於所有的pair集合(x,y),有多少種獲勝的可能。 [分析] 有時候轉變一下思維方向非常重要。 這道題中一般思路是想到5000*5000後快速判定勝負 然而,實際上我們可以對於m個位置的每一個,處理其所對應的所有集合。 細節上我們是用bitset實現的。為什麼要用bitset,因為我們想壓位,然而因為位數太多(>64)而無法用普通變數實現。 於是這道題如果開bitset,那就是開50個長度為5000的bitset,a[i][j]就表示對於數字i,集合j中是否有這個數字。 我們處理的過程,肯定是一個個集合順序列舉,列舉這個集合是洞的情況(5000*50的列舉量)。如何判定當某個洞有沒有進球呢? 只需要列舉[上一個洞+1 ~ 這一個洞]之間的所有位置,把所有or值or到一個bitset tmp中,(就是對一定位置的bitset做or運算) 然後我們檢視tmp,tmp是長度為n的bitset,tmp[x] == 1 就表示著,集合x是可以打球入這個洞。 接著把所有洞的進球集合tmp異或一下(就是求所有tmp的異或結果,也是一個長度為n的bitset), 最後的異或值p[x]就表示,以集合x的所有球,進當前集合的所有洞,能進球的洞數為奇數還是偶數。 不停使得ans+=p.count(),就能得到最後的答案。 使用舉例6 HDU5036 2014 ACMICPC Asia Regional Beijing Online E Explosion 每個房間若干鑰匙 沒有鑰匙需要爆破 開啟所有房間的爆破數期望 [題意] 有n(1000)個房間,每個房間的門必須用其特定鑰匙才能開啟 每個房間有若干個(0~n)把鑰匙(編號1~n) 我們初始沒有鑰匙,當我們手中的鑰匙再也無法開任何一扇門的時候,我們必須要爆破一個房間 問你我們最終進入所有房間的期望爆破數 [分析] 首先要求出對於每個門,有多少扇門的開啟會導致這扇門的開啟。 這個利用floyd閉包傳遞實現 for(k)for(i)for(j)if(a[i][k]&&a[k][j])a[i][j]=1 然而這個時間慢會TLE,於是我們用bitset優化 <1> if(a[i][k])a[i][j]|=a[k][j] <2> if(a[i][k])a[i]|=a[k] 處理之後,a[i].count()就是炸i能開啟的房間數,接下來要算期望。 有一種算期望的方法我認為是對的,就是—— 用p[i]表示能導致第i個房間被開打的房間,全部都排在第i個房間後面的概率, 只有在這時,對於第i個房間就必須強行炸一次,於是其實答案就是(∑p[i])。 p[i]怎麼算呢?這個點排在整個全排列的前面,概率顯然是1/k啦 所以總的期望就是先是∑(1/k[i])啦 使用舉例7 HDU5890 2016 ACMICPC Asia Regional Qingdao Online L 50個數扔3個後選10和是否可為87 [題意] 有n(50)個數 有m(1e5)個詢問 對於每個詢問,我們需要扔掉xyz三個數 問你剩下的數中,是否可以恰好選出10個,使得其和恰好為87 [分析] 對於暴力的揹包,複雜度是n*10*87的。這道題我們可以加一些優化—— 方法一:我們預處理出一組解,得到我們有哪10個數可以構成87。 然後,接下來的移除的3個數,如果都在這10個數之外,我們就直接輸出Yes,否則我們再做DP。 這種方法,有C(40,3)種情況被我們簡化掉了,就只剩下了1e4種DP需求 方法二:我們對同樣的數做判重,雜湊得到最小表示法,減少狀態數 方法三:我們可以通過字首和與字尾和做2個DP bef[i][j][k][w] 表示用[1~i]數中的j個,嚴格沒有選第k個,和為w是否可行 beh[i][j][k][w] 表示用後[i~n]數中的j個,嚴格沒有選第k個,和為87-w是否可行 那麼對於一組詢問, (bef[i][j][k] & beh[i][j][k]).any()就是答案 使用舉例8 HDU5808 BestCoder Round 86E Price List Strike Back 距離範圍、區間範圍商店購物 使得價值和恰為m [題意] 有n(20000)個商店,第i個商店距離家的位置為d[i](1e9),賣的物品的價值為v[i](100) 有m(1e5)個詢問(l,r,dis,sum) 問你,如果對[l,r]內,距離我家距離不超過dis的商店做購物(可以對於v[l~r]的每一者選擇或不選擇), 能否能夠恰好使得商品總價值為sum [分析] 首先,因為涉及到一個距離的影響。 我們要把距離的影響消掉,所以可以考慮對—— "所有商店距離家的距離" "所有詢問距離家的距離" 這兩樣東西按照升序排序,這樣使用雙指標,我們就可以使得在處理一個詢問的時候,只考慮了所有距離範圍內的商店。 在過程中需要用樹狀陣列維護字首和,用於求出區間範圍內某種價值的物品有多少件。 然後開始處理詢問—— 1,樹狀陣列的查詢量最多為m*100*log(n),複雜度為1e8 2,然後做DP,這裡的狀態轉移是f[i]|=f[i-物品價值],這個顯然可以用bitsetDP實現。 這裡需要一些優化,對於揹包上限為m,物品價值為i,顯然物品數量最多為m/i 這樣子的話,我們揹包的大小最多隻會為m/1+m/2+...+m/m為mlogm級別(即800左右) 這時做bitsetDP的複雜度上限為800 * 100 / 32,複雜度為m * 800 * 3,複雜度為2e8 我們可以用二進位制拆分再次優化。 或者在bitsetDP的時候,DP的終止條件為我們用當前的物品無法再更新出 甚至這兩個優化可以加在一起。 使用舉例9 2015廣東工業大學新生賽E GDUT的實驗室 十進位制與二進位制的比較 [題意] 給你二進位制IP編碼和十進位制IP編碼,讓你判斷兩者是否一致。 [分析] c++的讀入—— 預設資料按十進位制輸入輸出。如果要求按八進位制或十六進位制輸入輸出,在cin或cout中必須指明相應的資料形式。 oct為八進位制,hex為十六進位制,dec為十進位制。即cin>>oct(hex)>>x c的讀入—— %o 讀入八進位制數 %x 讀入十六進位制數 bitset的讀入—— for (int i = 0; i<4; ++i)cin >> b[i], cin.get(); bitset讀入之後, for (int i = 0; i<4; ++i)if (a[i] != b[i].to_ulong())return FALSE; bitset.to_string() bitset.to_ulong() bitset.to_ullong() 使用舉例10 2015-2016 XVI Open CupJ Judgement n人投票前後權威值改變是否有實際區別 [題意] 有n(100)個法官,每個人有一個權威性a[]。 對於一次投票,如果投贊成票人數的權威性之和>=p,那麼就認為投票有效,否則無效。 然而,這些人的權威性由a[]被改變成了b[],決定投票有效與否的閾值也由p變成了q。 然後問你,前後的改變是否有實際區別。 如果有,輸出同樣的投票對應不同的結果的投票方案。 各種與權威性相關的數值a[]、b[]、p、q都在[1,1e6]範圍。 [分析] 1,TLE了不一定是時間不夠,可能是死迴圈了。 2,前驅記錄輸出方案的方法一定要顧忌到是否存在環 3,我們可以通過交換陣列的方式實現相似過程的統一過程化處理。 首先,面對複雜度的問題,我們可以先思考樸素好想的暴力解法。 顯然,如果投票產生了差別,可能有兩種情況—— 之前權威之和 >= p,現在 < q;之前權威之和 < p,現在 >= q 我們可以考慮一個揹包 f[i][j]表示考慮了前i個人,這些人之前的權威性之和為j,對應的最大後權威性之和 d[i][j]表示考慮了前i個人,這些人之前的權威性之和 不妨先假設p達標,q不達標於是,我們用f[i][j]表示—— 前i個人參與投票,當投票對應的a[]的權威值為j時,所對應的最小b[]。 那麼有狀態轉移gmin(f[i+1][j+a[i]],f[i][j]+b[i]) 然而j+a[i]與f[i][j]+b[i]都可能爆炸。我們只要控制兩者的上限不超過p和q即可(因為實際意義都相同) 最後只要檢測f[p]是否<q即可。 這個DP的複雜度是1e8的,是有可能跑得過的。 但是剩下一個重要的問題,如何輸出具體的方案。 一種比較常見的做法,是在每個f[i][j]的j下標對應前驅。 然而,在達到頂端p的時候,這個j有可能指向自己,這樣就GG了。 於是,我們需要用一種特殊的記錄方法—— 我們觀察到,人數n只有100,100雖然超出了狀壓的範圍, 然而還是可以用分開狀壓(2個LL)或者用bitset儲存記憶這個狀態是由哪些人貢獻來的。 不過這樣空間複雜度依舊是1e8,是我們所不支援的。 然而我們並不需要f[i][j]的i維度,只要列舉i逆序j更新bitset即可。 bool solve(int o) { //f[x] 表示當我們狀態的投票權威性之和為x時,所對應的對方最小投票權威性之和 MS(f, 63); f[0] = 0; int top = 0; for (int i = 1; i <= n; ++i) { for (int j = top; j >= 0; --j) { int x = min(j + a[o][i], v[o]); //下一個我方狀態點 int y = min(f[j] + a[o ^ 1][i], v[o ^ 1]); //下一個對方狀態點 if (y < f[x]) { f[x] = y; d[x] = d[j]; d[x][i] = 1; } } top = min(top + a[o][i], v[o]); if (f[v[o]] < v[o ^ 1]) { puts("NO"); for (int i = 1; i <= n; ++i)printf("%c", d[v[o]][i] ? '1' : '0'); puts(""); return 1; } } return 0; } 使用舉例11 2016百度之星 - 複賽(Astar Round3)E 長度m的串匹配n個集合 [題意] 給你一個長度為m(2e6)的文字串, 我們想要找出其所有的符合特定模式的子串位置。符合特定模式是指—— <1> 子串長度為n(500) <2> 第i個字元需要在給定的字符集和Set[i]中(即給出了n個字符集,字符集中的合法字元為數字或大寫字母) [分析] 依然先思考暴力的做法—— 我們需要列舉所有起點(m個),再列舉連續的(n長度),然後做字符集的匹配。 但是這個複雜度是1e9的,不能被支援。 我們發現,實際上我們會需要用到這麼一個東西,判定位置i的字元是否在第j個集合之中(依然是1e9的複雜度) 而這個東西是大概可以使用bitset做優化的。 然而首先考慮一個複雜的DP做法,這個DP對於相同的尾端點統一化遞推處理,這樣才方便bitset優化—— f[i][j]表示匹配到第i個位置,以i為尾端的條件下,是否能夠實現匹配長度為j(即1~j的匹配都實現) 那麼就有 f[i][j]=f[i-1][j-1] && (s[i] in set[j]) 程式就是—— for(int i = 1; i <= l; ++i) { f[i][0]=1; for(int j = 1; j <= n; ++j) { f[i][j] = f[i-1][j-1] && (s[i] in set[j]) } } 顯然,對於此,我們可以使用bitset進行優化—— f[i] = (f[i-1] << 1) & (b[s[i]]) [j] [j - 1] [j] 即做一個預處理b[s[i]][j]表示字元s[i]是否在第j個集合中出現。 for (int i = 0; s[i]; ++i) { bt1[0] = 1; bt2 = bt1 << 1 & b[s[i]]; if (bt2[n]) printf("%d\n", i - n + 2); swap(bt1, bt2); } 使用舉例12 Codeforces Round 360 (Div 1)C The Values You Can Make 若干硬幣總數為m子集可能方案數 [題意] Pari wants to know the values x such that there exists a subset of coins with the sum m such that some subset of this subset has the sum x, 題目是說,要在恰好構成m的硬幣的子集上求其子集能夠達成sum為x的x數量 換句話說,舉個例子 5 6 3 3 2 2 2 ans = 0 2 3 4 6 ,並沒有5,因為5打破了集合的內部性 硬幣數為n(500),揹包上限為m(500) [分析] 這個DP的思想要好好學習一下——2side DP大法 就是把複雜度看似為2^n的DP,用n^2的方法實現 我們用f[i][j][k]表示枚舉了前i個物品,其中一部分物品的sum為j,另外一部分物品的sum為k的可行性 顯然初始化f[0][0][0]=1 DP轉移是列舉可行的f[i-1][j][k],然後新的物品可以加入j或者k中去 這個DP的時間複雜度為O(n^3),空間複雜度為O(n^2),因為第一維度可以省略 這樣DP完之後,我們就可以根據最後的具體合併方案,而得到答案了。 具體上可以使用bitset優化 b[j][k]表示考慮了前i枚硬幣,總的硬幣面額為j,左半邊的硬幣面額為k的狀態可達性 優化成bitset b[j]。然後更新包括—— b[j + x] |= b[j] 硬幣放在右邊 b[j + x] |= b[j] << x 硬幣放在左邊 使用舉例12 中國大學生程式設計競賽中南邀請賽(重現)I Substring Query 匹配串中各模板串出現次數 [題意] 字串長度為5e4,操作個數為1e5 詢問有兩種: 1:p ch 修改s[p]為ch 2:0 string 詢問string在s中出現的次數 [分析] for (int i = 0; i < 26; ++i)b[i].reset(); for (int i = 1; i <= l; ++i)b[s[i] - 'a'].set(i); //依然是形成了一個 字元 -> 位置 的套路 while (q--) { int op; scanf("%d", &op); //詢問是即時完成的 if (op == 0) { scanf("%s", ss + 1); int ll = strlen(ss + 1); //一開始得到了哪些位置可以是匹配ss[1]的位置 bt = b[ss[1] - 'a']; //然後我們把匹配位置 >> (i - 1) 與1對其,得到了其延展 for (int i = 2; i <= ll; ++i) bt &= (b[ss[i] - 'a'] >> (i - 1)); printf("%d\n", bt.count()); } //修改只需要對bitset做暴力修改 else { char ch; scanf(" %c", &ch); b[s[op] - 'a'].reset(op); b[ch - 'a'].set(op); s[op] = ch; } } 使用舉例13 HDU5745 2016 Multi-University Training Contest 2L La Vie en rose 目標串多少子串可以被原始串做相鄰交換得到 [題意] 給你一個長度為n(1e5)的目標字串a 我們有一個長度為m(min(5000,n))的基礎串b, 對於這個目標字串,顯然有n-m+1個長度為m的子串 問你,在這些子串中,有哪些子串,是可以通過對基礎串一些相鄰位置的交換,得到b。 即,我們可以選出若干個基礎串的若干個位置p[],使得p[i+1]>p[i]+1, 然後我們swap(p[i],p[i]+1),就可以實現變形後的基礎串與目標字串的匹配 [分析] 儘管暴力可過,但是正解是bitset,我們依然需要學習一下—— 這題的暴力做法,其實本身就比較相似於一個DP。 這道題定義f[i][j][k]表示—— a串匹配到位點a[i] b串匹配到位點b[j] j這個位置的匹配狀態為k(0表示b[j]要與b[j-1]交換,1表示沒有交換,2表示b[j+1]交換) 轉移方程是這個樣子的—— dp[i][j][0]=dp[i-1][j-1][2] & (a[i]==b[j-1]) //a[i-1]與b[0~j-1]匹配了,且a[i]實際要與b[j-1]做匹配 dp[i][j][1]=dp[i-1][j-1][0] & (a[i]==b[j]) | dp[i-1][j-1][1] & (a[i]==b[j]) //a[i-1]與b[0~j-1]匹配了,且a[i]實際要與b[j]做匹配 dp[i][j][2]=dp[i-1][j-1][0] & (a[i]==b[j+1]) |dp[i-1][j-1][1] & (a[i]==b[j+1]) //a[i-1]與b[0~j-1]匹配了,且a[i]實際要與b[j+1]做匹配 ========================================================================= 不過該程式的常數巨大,後來還是被加強加版資料卡得TLE了。 我們再來學習另外一種快些的bitset做法,該做法是把a做bitset壓位展開的。 我們開bitset<N>dp[3],再開bitset<N>w[26]w[i],表示對於字元i,其在a[]串的哪些位置出現。 初始化dp[0][i]都為1,表示匹配b的長度為0的條件下,以i為a[]的匹配起點,都是可以匹配的。 然後我們做m輪次的匹配,每次取出b中的字元。顯然,正常的匹配是這樣子的—— dp[nxt]=dp[now]&(w[b[i]]>>i),即以a[]的每個位置為起點,我們檢視每個位置往後走i(i∈[0,m))位的字元,是否依然可以匹配到b串, 不過還存在了交換的匹配方式—— dp[nxt]=dp[pre]&(w[b[i]]>>(i-1))&(w[b[i-1]]>>i),即考慮交換的匹配。 使用舉例14 ICPCCamp2016 Day5L 希哥一血 [題意] 有n(5e4)個點 有m(1e5)條邊,每條邊都互不相同 我們希望找到三條邊,構成一個最小的三角形。 [分析] 這道題的實現需要一個列舉的過程,兩個貪心的思想,和一個bitset的資料結構 <1>列舉的過程—— 對於構成三角形的三條邊,不妨假設a < b < c,並設dif = c - b。 滿足三角形的限制,除了a < b < c這個假設外,就只需要滿足a > dif 於是我們列舉dif從1到maxdif(即maxv - minv),列舉的複雜度為m <2>第一個貪心的思想—— 此處已知了dif,應該對應得到滿足條件的最小的a,因為這樣還可以使得我們對於 b c 擁有更大的選擇空間 <3>第二個貪心的思想—— 對於一個固定的a,我們列舉要找到合法的解(b,c),同時,還要對應使得b儘可能小,這樣才會貪心使得面積儘可能小 <4>於是,問題變成了—— 我們如何快速找到邊長b和b+dif(且有限制條件b+dif>a)呢 之前的複雜度已經有了O(m)的乘法系數了,這裡可以使用一個bitset結構維護。 我們用一個bitset陣列B[],B[x]表示是否有邊長為x的邊存在,1表示存在。於是使得T = B & (B >> dif) 我們得到的T,如果T[x] = 1且x > a,那麼x就對應一個可行的b的解。於是我們需要實現在bitset中查詢最小>a的1, 希哥的做法是我們把bitset轉int,然後查詢比x大的int位是否有不為0的數,如果有,那就找到了所對應的b和c 利用這樣的a、b、c,我們用海倫公式直接求得面積即可。 強轉函式:UI* v = (UI*)&T; //把bitset轉化為UI */ bitset<N>out[N];////out[i][j]是原始圖,是正向邊 bitset<N>in[N];//in[j][i]是反向圖,是反向邊 int main() { while (~scanf("%d", &n)) { LL ans = 0; for (i = 0; i<n; i++)out[i].reset(), in[i].reset(); for (i = 0; i<n; i++) { scanf("%s", s); for (j = 0; s[j]; j++)out[i][j] = in[j][i] = (s[j] == '+'); } for (i = 0; i<n; i++) { for (j = 0; j<n; j++)if (out[j][i]) { ans += (out[i] & in[j]).count(); } } printf("%I64d\n", ans / 3); } return 0; }