總結一下,這幾天的學習情況大概是這樣的。
8.7:後綴數組
8.8:字典樹
8.9:最大流
8.10:考試(無內容)
8.12:組合數學+拓撲圖+強連通分量
後綴數組
後綴數組的主要內容就是兩個算法:倍增算法以及基數排序。這是兩個很馬叉蟲的算法。
先闡述一下倍增算法。這是一個對字符串以及其後綴排序大小的排序,速度灰常快,只需要執行
是這樣的,首先我們根據字符串S的每一個字符串長度爲1的字符串進行比較,不用多說,這是線性時間就可以得出的結果。然後,我們可以發現,只根據第一個字母排序是不能得出每一個字符串的唯一大小的。所以我們要進行其他的高級運算。
在得出字符串長度爲1時的大小之後,我們就可以通過獲取第2位,或者說得到總長度爲2(其實應該叫做
然後就很好理解了,既然
如果還不懂,那就來看我這張從度娘那盜來的圖吧~
解決了倍增算法,我們就來了解一下什麼是基數排序吧。這也是個很有趣的東西。
何謂基數排序,那就是一個根據基(礎)數(據)加上後來的輔助數據來解決一些多關鍵字排序的問題。其實叫做基數排序,我感覺就是桶排序的加強版。因爲桶排序是儲存一個關鍵字,其實也就是直接得出答案。而基數排序就要記錄多個關鍵字,然後將它們合併。因爲這些數據是不變的,是固定的,而且基數排序只是自身的後綴進行處理,也就是倍增算法的具體操作罷了。所以,我們就開始解決這個簡單且simple的問題吧。
首先,基數排序也是需要用第一次的數據做基礎的,從倍增算法中我們可以得知,這很簡明易懂。
Next Day
=================請叫我分割線=================
字典樹
字典樹,字典樹,顧名思意,就是一棵像字典一樣的樹。而每一條邊記錄的都是各種的字符。總而言之就是一種對不同字符的處理和解決。反正就是一種很樸素的方法,存放的時間爲字符串總長度,儲存空間一般是存儲 總長度*字符數(26之類的)。
好吧好吧。我承認我不太會講這個辣麼簡單的東西。所以我只好來講講關於字典樹的構造方法吧。
字典樹的構造方法有兩種。
第一種是用鄰接矩陣的方法,直接開 長度*字符狀態 的空間大小,第一維用來做數組模擬鏈表的下標,然後第二維用來做字符狀態的轉移和限制,這個二維數組裏面有一個一定要記的值,就是指向下一層的下標,因爲只有記錄這個才能把這個數組模擬成鏈表,不然直接用下標來做字典樹的話就不叫鏈表了。雖然這種方法很簡單也很常見,但是有某些陰險狡詐,卑鄙無恥的出題人總愛用這種硬性的空間卡你的數據範圍,按理來說只開 長度 個空間其實就可以存儲所有的內容了,但是爲了記錄狀態我們又不得不開多一維。那麼問題來了,這道題既然放在這,那就一定又解決的辦法,是什麼呢?就是我們神奇的指針大法!
第二種,沒錯就是指針。指針是個好東西啊!因爲指針不需要事先開好數組,只需要每一次訪問到一個新的節點就從空間裏申請一個節點,把指針指向新節點的地址,然後這個新的節點就可以拿來用了。所以指針基本上就只需要用 長度 個空間就可以解決這個問題。具體怎麼實現呢?其實跟第一種方法是一樣的,也是用鏈表的方法,記錄他的下一個,然後把他們鏈接起來就形成一個跟第一種方法一樣的鏈表了,唯一不同的是,第一種是事先開好空間大小,而第二種則是每次訪問到需要一個新的節點的時候,就申請一個新的節點。
差不多就是這樣了,我覺得還是舉個例子加強一下印象吧。
例如說,求‘aab’‘aabaa’‘aca’這三個字符串的最長公共前綴,怎麼辦呢?
看下圖。
講述的是一段悽婉的愛情故事,啊呸,什麼東西卡在我喉嚨裏。講述的是把這幾個字符串放進字典樹後的樣子,而每一個節點上都記錄一個數值,那就是被經過的次數,我們可以看到,根結點下面那個點被經過了三次,就說明他是所有字符串的公共前綴,然後在看到下面一層,發現左邊是被經過了兩次,另一邊是被經過了一次,所以就不是公共前綴了啊!所以我們只需要從根節點往下數,數到一個被訪問次數小於前面的節點,那麼從他的上一層到根結點都是這些字符串的最長公共前綴。
貼段程序壓壓驚~
題目意思很清楚:就是判斷輸入的電話號碼中是否有號碼是其他號碼的前綴,很顯然要用到字典樹。根據分析可知:
如果字符串Xn=X1X2….Xn是字符串Ym=Y1Y2….Ym的前綴,有在插入的時候有兩種情況:Xn在Yn之前插入,Xn在Yn之後插入。
1)如果Xn在Yn之前插入,那麼在插入Yn的時候必然經過Xn的路徑,此時可以根據判斷在這條路徑上是否已經有結點被標記已經構成完成的字符串序列來判斷是否存在Yn的前綴;
2)如果Xn在Yn之後插入,那麼插入完成之後當前指針指向的結點的數組中的元素必定不全爲0。
這是一道模板題,不需要多說什麼廢話。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
int point=0;
int val[10001*10][15];
int tree[10001*10][15];
char chr[10005][15];
bool check(char *a)
{
int len=strlen(a);
int p=0,state;
for (int i=0;i<len;i++)
{
int now=a[i]-'0';
if (i==len-1) state=-1;
else state=1;
if (val[p][now]==-1)
return 0;
if (val[p][now]==1&&state==-1)
return 0;
if (tree[p][now]==0)
{
tree[p][now]=++point;
val[p][now]=state;
}
p=tree[p][now];
}
return 1;
}
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
point=0;
memset(tree,0,sizeof tree);
memset(val,0,sizeof val);
int n;
scanf("%d",&n);
for (int i=0;i<n;i++)
scanf("%s",chr[i]);
bool flag=1;
for (int i=0;i<n;i++)
{
flag=check(chr[i]);
if (!flag)
{
printf("NO\n");
break;
}
}
if (flag)
{
printf("YES\n");
}
}
return 0;
}
就是這樣。
下一題走你!
【下一題是一道用第一種方法會被卡空間範圍的題目,所以我用指針來做,讓你們在座的各位辣雞見識一下】
題意是給定如干個字符串,將其兩兩組合,有n × n種組合方式,求有多少個迴文組合。
You can assume that the total length of all strings will not exceed 2,000,000. Two strings in different line may be the same.
需要注意的就是那一串數字,你沒有看錯,長度就是這麼長,足足又兩百萬!!額,好吧,是一共兩百萬個字符~
然後,就沒有然後了,這個數據範圍還是比較大的。
所以指針走你。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
const int N=2000002;
struct Trie
{
int val,num;
Trie *nxt[27];
};
Trie node[N];
Trie *root;
int begin[N],end[N],d[N*2+10];
char S[N],T[N],now[N*2+10];
bool flag[2][N],tmp[N*2+10];
long long ans=0;
int pos=0;
void manachar(char *a,bool *ok)
{
int len=strlen(a);
now[0]='!';
now[1]='z'+1;
for (int i=0,j=1;i<len;i++)
now[++j]=a[i],now[++j]=now[1];
len=strlen(now);
int r=0,mid=0;
for (int i=1;i<len;i++)
{
if (r>i)
if (d[2*mid-i]<r-i)
d[i]=d[2*mid-i];
else d[i]=r-i;
else d[i]=1;
while(now[i-d[i]]==now[i+d[i]])
d[i]++;
if (i+d[i]>r)
r=i+d[i],mid=i;
}
int j=0;
for (int i=0;i<len;i++)
if (i+d[i]==len&&i!=len-1)
tmp[i-d[i]]=1;
for (int i=0;i<len;i++)
if (now[i]>='a'&&now[i]<='z'||now[i]=='!')
ok[j++]=tmp[i];
ok[j-1]=0;
for (int i=0;i<len;i++)
d[i]=0,now[i]=0,tmp[i]=0;
}
void add(char S[],int l,int r)
{
Trie *temp=root;
for (int i=l;i<=r;i++)
{
int ch=S[i]-'a';
temp->val+=flag[0][i];
if (temp->nxt[ch]==NULL)
temp->nxt[ch]=&node[pos++];
temp=temp->nxt[ch];
}
++temp->num;
}
void query(char S[],int l,int r)
{
Trie *temp=root;
for (int i=l;i<=r;i++)
{
int ch=S[i]-'a';
temp=temp->nxt[ch];
if (temp==NULL) break;
if ((i<r&&flag[1][i+1])||i==r)
ans+=temp->num;
}
if (temp) ans+=temp->val;
}
int main()
{
int n,l,L=0;
scanf("%d",&n);
root=&node[pos++];
for (int i=0;i<n;i++)
{
scanf("%d",&l);
scanf("%s",S+L);
for (int j=0;j<l;j++)
T[L+j]=S[L+(l-1-j)];
begin[i]=L;
end[i]=L+l-1;
manachar(S+begin[i],flag[0]+begin[i]);
manachar(T+begin[i],flag[1]+begin[i]);
add(S,begin[i],end[i]);
L+=l;
}
for (int i=0;i<n;i++)
query(T,begin[i],end[i]);
printf("%lld\n",ans);
return 0;
}
大力戳我!沒錯要題目就是要戳這裏!
【媽卵,懶得翻譯了,直接給鏈接自己看題目sa~
還有一道很有意思的題目,是一道存儲二進制狀態的題。這道題是一道在樹上做的題【咳咳,別想太多,在樹上你怎麼做題?】這道題要求在一棵樹上任意兩個點的路徑異或和最大!!!
這道題是字典樹?字典樹?字典樹?沒錯,還真是。首先我們要解決的是,將這個問題轉化一下,變成另一個樣子。我們知道,X^X=0【不懂的小夥伴讓老司機度娘帶你去科普】所以當兩個節點同時連接根結點的時候,這兩條路的異或和正好等於它們到根結點這兩條路的異或和。是不是很神奇,其實就相當於把他們最近公共祖先到根結點的路徑異或和去掉了,所以不會有什麼影響。啊呸,誰說沒有影響的,這就可以把問題轉化成求這棵樹上每一個點到公共祖先的路徑異或和了!然後我們就可以把這些異或和放進字典樹中。有人肯定要問,不都是一個數字嗎?怎麼放啊?切,那你就把它變成N個數字lor。因爲異或這種運算是相同爲“1”,不相同爲“0”的。所以我們就可以把它們的二進制放進去,然後就很簡單了。我們把所有路徑異或和放進字典樹後,我們就可以根據枚舉所有到根節點的路徑異或和然後找字典樹裏面儘量跟這個數相反,也就是異或爲“1”的數。但是還有一個小細節需要注意,那就是最大數據也只有30位,因爲<2^31所以只有30位,所以我們就需要把這30位都記錄下來,因爲高位的大小比後面的更能影響整個數的大小。
例如說:
10000
01111
即使後面的全都滿足又怎麼樣?老子,啊呸,勞資第一位就比你大,你能咋地?嗯?不服?嗯,沒錯,就是這種情況。
程序走你!【注:因爲沒有卡空間大小,所以我就懶得用指針了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
int edge[4*100001],edgew[4*100001],Next[4*100001];
int head[2*100001],sum[2*100001],tree[35*100001][3];
bool go[2*100005];
int e=0,point=0;
void addedge(int u,int v,int w)
{
edge[e]=v; Next[e]=head[u]; edgew[e]=w; head[u]=e++;
edge[e]=u; Next[e]=head[v]; edgew[e]=w; head[v]=e++;
}
void dfs(int k,int val)
{
sum[k]=val;
go[k]=1;
for (int i=head[k];i!=-1;i=Next[i])
{
if (!go[edge[i]])
dfs(edge[i],edgew[i]^val);
}
}
void add(int val)
{
int p=0;
for(int i=30;i>=0;i--)
{
int state=0;
if (val&(1<<i))
state=1;
if (!tree[p][state])
tree[p][state]=++point;
p=tree[p][state];
}
}
int query(int val)
{
int p=0;
int num=0;
for (int i=30;i>=0;i--)
{
int state=0;
if (val&(1<<i))
state=1;
if (tree[p][1-state])
{
num|=1<<i;
p=tree[p][1-state];
}
else p=tree[p][state];
}
return num;
}
void clean()
{
memset(edge,0,sizeof edge);
memset(edgew,0,sizeof edgew);
memset(Next,0,sizeof Next);
memset(head,-1,sizeof head);
memset(tree,0,sizeof tree);
memset(go,0,sizeof go);
memset(sum,0,sizeof sum);
}
int main()
{
int n;
while (~scanf("%d",&n))
{
point=0;
clean();
for (int i=0;i<n-1;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
dfs(1,0);
for (int i=0;i<n;i++)
add(sum[i]);
int ans=0;
for (int i=0;i<n;i++)
ans=max(ans,query(sum[i]));
printf("%d\n",ans);
}
return 0;
}
Next Day
=================請叫我分割線=================
最大流
最大流的概念:這個東西主要是拿來處理一個有源點和匯點的一個有u個點,v條邊的有向圖G(u,v)。每條邊上面都有一個權值,當這條邊流過了一定的東西之後,是不能再通過這條邊流其他東西了,而這個決定能不能流【咳咳,don’t think too more】就在於它的流量是多少。所以怎樣使流到匯點的值最大就是這個算法的存在意義。
最大流的一些小技巧:因爲這不是什麼神奇的算法,也不是那種一股腦可以完成的東西,所以我們就需要一個【悔棋】按鈕。啊呸,說馬叉蟲話。總之就是這個意思,我們需要將一些流量等價的從另外一個方向流出去,這是可行的!至於怎麼完成,可以透露的是,這叫做反向邊,正向邊的殘量減少時,反向邊的殘量就增加。這是相對應的。然後就是因爲這個東西,我們就會使用大量的時間去反覆更新。
【注:還有一些需要注意,這是我第一次做的時候錯誤的地方,挺值得分享的。
那就是,流量是相對於邊來說的,而不是一個點,一個點是不叫做流量的。
還有我們只需要記錄一個殘量就足夠了,因爲殘量=總量-已用流量。記錄一個值明顯比兩個好多了。
還有還有,要記錄每一條邊上的最小瓶頸,因爲根據木桶原理,流量的多少不是根據最長的那條邊,而是最短的那條邊決定的。
最後,要記得將數組清空。
啊!終於熬到貼程序的地方了!
依舊是放題目鏈接:
沒錯,要題目就猛戳這裏!!!
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
int n,m;
int re[205],from[205],a[205][205],que[2000000];
bool go[205];
int maxstream(int s,int t)
{
memset(que,0,sizeof que);
int sum=0;
while (true)
{
memset(go,0,sizeof go);
memset(re,0,sizeof re);
go[s]=1;
re[s]=100000000;
int head=0,tail=0;
que[0]=s;
while (head<=tail)
{
int u=que[head];
head++;
for (int i=1;i<=t;i++)
{
if (!go[i]&&a[u][i]>0)
{
go[i]=1;
que[++tail]=i;
from[i]=u;
re[i]=min(re[u],a[u][i]);
}
}
}
if (re[t]==0) break;
for (int i=t;i!=s;i=from[i])
{
a[i][from[i]]+=re[t];
a[from[i]][i]-=re[t];
}
sum+=re[t];
}
return sum;
}
int main()
{
while (~scanf("%d%d",&n,&m))
{
memset(a,0,sizeof a);
for (int i=1;i<=n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[u][v]+=w;
}
printf("%d\n",maxstream(1,m));
}
return 0;
}
差不多就這樣了。
嗯。
Next Day
=================請叫我分割線=================
考試總結
蛤蛤蛤蛤蛤!
好吧,155分沒什麼好笑的。做了三題,第一題本該滿分的。沒錯說得的就是你,出題人,看什麼看?要不是你的mod沒有寫上去,我會只有這麼一丟丟分?然後,第二題,理所當然的滿分了。第三題就有點坑爹了。我的flag立錯位置了,所以大部分數據都沒過,而且我的這個算法是有那麼一丟丟的問題,很容易超市,需要在處理一個反向next【這道題是KMP】才能達到O(N)級別的算法。不然的話就要用擴展KMP來做。坑爹的題目說了巴拉巴拉一大堆,然後又說KMP,KMP,KMP,別™KMP了傷身又傷腎【咳咳,沒什麼,就當沒看見】。於是我就天真的用KMP來做了。前面的做法與標程一致,只不過少處理了一個數組,時間複雜度升了一維,其實好運的話可以卡過比較多的數據的。至於第四題,還有五分鐘,看看題目消遣消遣就好。【消音】【消音】【消音】的,要不是爲了看懂第一題浪費了我這麼多時間,我會介麼慘???好吧,最後一題其實也就是搜索加最大流,因爲最大流只能處理二分圖,所以我們要把三分圖【亂入的名字】合併成二分圖。由於木桶原理,最後的答案是由最短的那個決定的【第一次看見短小有人權啊!沒什麼,當我沒說】於是我們在把三分圖讀入進二分圖的時候,我們需要在兩個節點之間去min值。然後最大流!最大流!最大流!嗯。就是這樣。
先看題【爲什麼沒有鏈接!!爲什麼!!爲什麼!!】
大小不一,別怪我,要怪就怪畫圖軟件,怪windows系統,不關我事,盡情甩鍋。
好了,又是貼程序的歡樂時光。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std ;
struct Tline
{
double xl,yloc;
}line[300005];
long long val[300005];
bool cmp(Tline a,Tline b)
{
return a.xl<b.xl;
}
int main()
{
freopen("trokuti.in","r",stdin);
freopen("trokuti.out","w",stdout);
int n;
cin>>n;
for (int i=0;i<n;i++)
{
double a,b,c;
cin>>a>>b>>c;
line[i].xl=-a/b;
line[i].yloc=-c/b;
}
sort(line,line+n,cmp);
int flag=1;
val[flag]=1;
for (int i=1;i<n;i++)
{
if (line[i].xl==line[i-1].xl)
{
if (line[i].yloc!=line[i-1].yloc)
val[flag]++;
}
else val[++flag]++;
}
long long tmp=(flag-1)*(flag-2)/2;//=(flag)*(flag-1)*(flag-2)/6*3/flag
long long ans=(flag)*(flag-1)*(flag-2)/6;
for (int i=1;i<=flag;i++)
ans+=(val[i]-1)*tmp;
cout<<ans<<endl;
return 0;
}
至於哪裏需要mod,什麼時候要重測,嗯根本就沒有說過嘛,懂你的。
程序猿走你!
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std ;
const int INF=2000000000;
struct Tbe
{
int a,b,c=0;
}be[100005];
struct Tstack
{
int want,costl,costr,num;
}stack[100005*10];
int cost[10005],begin[10005],end[10005];
bool cmp(Tbe a,Tbe b)
{
return a.c<b.c;
}
int main()
{
freopen("dwarf.in","r",stdin);
freopen("dwarf.out","w",stdout);
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++)
cin>>cost[i];
for (int i=1;i<=m;i++)
cin>>be[i].c>>be[i].a>>be[i].b;
sort(be+1,be+m+1,cmp);
be[0].c=be[1].c-1;
be[m+1].c=be[m].c+1;
for (int i=1;i<=m+1;i++)
if (be[i].c!=be[i-1].c)
{
begin[be[i].c]=i;
end[be[i-1].c]=i-1;
}
int top=1;
int flag=1;
stack[1].want=0;
stack[1].costl=cost[1];
stack[1].num=1;
stack[1].costr=0;
while(flag<=top)
{
int now=stack[flag].num;
for (int i=begin[now];i!=0&&i<=end[now];i++)
{
top++;
stack[top].want=flag;
stack[top].num=be[i].a;
stack[top].costl=cost[be[i].a];
stack[top].costr=INF;
top++;
stack[top].want=flag;
stack[top].num=be[i].b;
stack[top].costl=cost[be[i].b];
stack[top].costr=INF;
stack[flag].costr=0;
}
flag++;
}
while(top)
{
int now=stack[top].num;
int nxt=stack[top].want;
for (int i=begin[now];i<=end[now];i++)
stack[top].costl=min(stack[top].costl,stack[top].costr);
stack[nxt].costr+=stack[top].costl;
top--;
}
cout<<stack[top+1].costl<<endl;
return 0;
}
AC不解釋,就是這麼強!
讓你們感受下被炒雞長的題目支配的恐懼吧!
又是一段不AC的代碼。。。
不過我知道怎麼做也懶得改了。。。
就是這麼懶,就是這麼任性。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
const int mod=1000000007;
char chr[1000005];
int Next[1000005],num[1000005];
int main()
{
freopen("zoo.in","r",stdin);
freopen("zoo.out","w",stdout);
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",chr);
int len=strlen(chr);
Next[0]=Next[1]=0;
int k=0;
for (int i=1;i<len;i++)
{
while (k>0&&chr[i]!=chr[k])
k=Next[k];
if (chr[i]==chr[k]) k++;
Next[i+1]=k;
}
num[0]=0;
int flag=0;
for (int i=1;i<len;i++)
{
num[i]=num[Next[i]];
if ((Next[i]==0&&chr[i]==chr[0])||Next[i]!=0)
{
int tmp=i;
while (!(Next[tmp]<=flag))
tmp=Next[tmp];
if ((i+1)/2>Next[tmp])
{
num[i]++;
flag=Next[tmp]+1;
}
}
}
long long ans=1;
for (int i=0;i<len;i++)
ans=(ans*(num[i]+1))%mod;
printf("%lld\n",ans);
}
return 0;
}
最後一題求大神check一下,感覺是沒錯的。但是不是超時就是錯…
蛤蛤蛤蛤蛤,換文本了,是不是很氣啊~
咳咳,題目太長了,你叫我怎麼截圖??
太小你們又看不見。
樣例輸入
15 15 15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
樣例輸入
13
錯誤代碼還有三十秒到達戰場,碾碎它們。
全軍出擊~
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=2100000000;
int G,B,P;
int level[100];
bool bg[100][100],bp[100][100],pg[100][100];
struct Tdinic
{
int c,f;
}edge[100][100];
bool dinic_bfs()
{
queue<int>Q;
memset(level,0,sizeof level);
Q.push(0);
level[0];
int u,v;
while (!Q.empty())
{
u=Q.front();
Q.pop();
for (v=1;v<=G;v++)
{
if (!level[v]&&edge[u][v].c>edge[u][v].f)
{
level[v]=level[u]+1;
Q.push(v);
}
}
}
return level[G];
}
int dinic_dfs(int u,int cp)
{
int tmp=cp;
int v,t;
if (u==G)
return cp;
for (v=B;v<=G&&tmp;v++)
{
if (level[u]+1==level[v])
{
if (edge[u][v].c>edge[u][v].f)
{
t=dinic_dfs(v,min(tmp,edge[u][v].c-edge[u][v].f));
edge[u][v].f+=t;
edge[v][u].f-=t;
tmp-=t;
}
}
}
return cp-tmp;
}
int dinic()
{
int sum,tf;
sum=tf=0;
while(dinic_bfs())
{
while(tf=dinic_dfs(1,INF))
sum+=tf;
}
return sum;
}
int main()
{
freopen("freeopen.in","r",stdin);
freopen("freeopen.out","w",stdout);
memset(edge,-1,sizeof edge);
scanf("%d%d%d",&G,&B,&P);
for(int i=1;i<=G;i++)
for(int j=1;j<=B;j++)
scanf("%d",&bg[j][i]);
for (int i=1;i<=G;i++)
for(int j=1;j<=P;j++)
scanf("%d",&pg[j][i]);
for (int i=1;i<=B;i++)
for(int j=1;j<=P;j++)
scanf("%d",&bp[i][j]);
for (int i=1;i<=B;i++)
for (int j=1;j<=P;j++)
if (bp[i][j])
for (int k=1;k<=G;k++)
if (bg[i][k]&&pg[j][k])
edge[i][B+k].c=1;
G=G+B+1;
for (int i=1;i<=B;i++)
edge[0][i].c=0;
for (int i=B+1;i<G;i++)
edge[i][G].c=0;
printf("%d\n",dinic());
return 0;
}
啊啊啊啊啊!
Next Day
=================請叫我分割線=================
組合數學
其實都沒什麼東西學,早都學過了,但還是總結一下吧。
總結個屁。
哼唧,就是這麼膨脹,地心引力根本抓不住我。
我就是要上天,和太陽肩並肩。
詳情請見:戳我你就上天了
下一個!
拓撲圖+強連通分量
這個就很高級了。其實還不是那【消音】樣。所謂拓撲圖,就是一些點,分別指向另一個點,然後每個點有入度和出度。但是還有可能形成環。所謂強連通分量,其實不要被這個名字嚇到,他只是虛有其表罷了。很簡單很simple的。其實就是指一個圖是完全連通的,算了,先說連通分量吧。連通分量就是拓撲圖中,爲什麼叫拓撲圖,就是因爲他是可以拓撲排序的,然後把入度爲0的點全部刪掉,最後會剩下一個環或者多個環,然後,這些環就叫做連通分量,而所有的環可以互相連接時,這些東西就叫做強連通分量。
好了,讓我來告訴你,強連通分量有什麼用吧。
例如說:有N個人,每個人都有他自己心目中的老大。假如A認爲B是老大,B認爲C是老大,那麼C就是A的老大。依此類推。但是,如果A認爲B是老大,B認爲C是老大,C認爲A是老大,那麼,A,B,C都是老大。然後,不要以爲這是單純的並查集,因爲,老大這個詞不單純,所以不是單純的並查集【咳咳,別跑題】然後,我們就可以找到強連通分量,而強連通分量就是通過拓撲圖,刪掉所有入度爲0的節點,直到只剩下一個強連通分量,這就是總的做法。
是不是很神奇!
纔不。
so easy!
媽媽再也不用擔心我的學習【咳咳,又亂入了】
好了好了。
是時候結束了。
Next Day
Next你ma了ge【嗶】
/再見/再見
END.