Bitset模板 Bitset題型大薈萃

以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;
}


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