[CodePlus2017][11月賽][切題]

似乎找不到什麼雞湯來安慰那個pkuwc滾粗的我

聽了大家的成績大概傷在day1 t2吧 11分 連暴力都不太會啊

不會騙分,不會寫暴力,不會總結,不會刷很多很多的題

自己蠢,自己頹,自己背鍋。

但是還是會忍不住難受

有些路,可能只有一個人走吧。

 

由於睡不着,就來總結一下CodePlus2017 11月月賽的題吧

前四題挺顯然的,就是類似第三題這種字符串中轉換的題可以思考用dp的方法做

比較巧妙的是t5 和 t6

在此具體講下

5109: [CodePlus 2017]大吉大利,晚上吃雞![最短路+拓撲排序+

傳遞閉包]

題目大意

官方題解(很良心了)

雖然題目中給定的是無向圖,但是實際上我們可以先從 S 出發求一遍最短路,然後問題變成了:“在有向無環圖(最短路圖)上,求有多少個滿足條件的點對 A,B,滿足從 S到 T 的所有路徑一定經過 A,B 其中一點,並且不存在路徑同時經過 A,B ”。

求解這到題目的一個關鍵點在於: 滿足條件的點對 A,B 具有特點:從 S到 A的方案數 × 從 A到 T 的方案數 + 從 S到 B的方案數 × 從 B 到 T 的方案數 == 從 S 到 T 的方案數。

所以在有向無環圖上用動態規劃求解路徑條數,再去掉 A可以到達 B 或 B 可以到達 A 的情況即可求解這到題目。

PS:方案數可能會爆掉怎麼辦?可以對方案數求餘一個大整數,如果覺得不夠的話可以求餘兩個大整數。

定義 F(X)= 從 S 到 X的方案數 ×從 X到 T的方案數 = 從 S 經過 X 到達 T 的方案數,所以滿足條件的點對 A,B 爲:

F(A)+F(B)=F(T)
A和 B 不能相互到達
對於條件 1 ,我們可以使用數據結構進行優化(使用std::map即可),而對於條件 2 ,我們可以使用 bitset 位壓進行加速,使得最終時間和空間都能夠承受。

時間複雜度: O(nlogn+nmw),其中 w是位壓的字長。

講一點實現上的知識點

1、判斷DAG上兩點是否可達:

可以用拓撲序+傳遞閉包(bitset優化解決)(f[i][j]=1代表i到j可達)(n*n/32的時間複雜度和空間複雜度)

代碼

void bfs1(int x1)
{
	f2[x1]=1; du1[x1]=0; q2.push(x1);
	while (!q2.empty())
	{
		int x=q2.front(); q2.pop(); b[x][x]=1; 
    	for (int i=head3[x];i;i=next3[i])
	     {
	     	int v=to3[i];  b[v]|=b[x]; //b[a][b]==1時a能到達b 傳遞閉包bitset優化
	     	du1[v]--; f2[v]+=f2[x],f2[v]%=mod; //f1起點到每個點條數 
	     	if (du1[v]==0) { q2.push(v); }
	     }
    }
}

 貼個模板題

注意這裏是有向圖但不是DAG(可能有環)

這裏似乎n*n*n/32 就夠 於是直接floyed水過,似乎直接暴力dfs求每個點能到那些點也能過。。。

不過似乎正解是用tarjan縮點變DAG+bitset(如上題)

給個floyd傳遞閉包的代碼段

 for (int i=1;i<=n;i++)
   {
   	  scanf("%s",s+1);
   	  for (int j=1;j<=n;j++)
   	  {if (s[j]=='1' || i==j) f[i][j]=1; } 
	  f[i][i]=1;
   }
   
   for (int i=1;i<=n;i++)
   for (int j=1;j<=n;j++)
   if (f[j][i]==1) f[j]|=f[i];
   for (int i=1;i<=n;i++) ans+=f[i].count();

2、統計

     直接枚舉每個A點,統計貢獻,然後去掉A點的影響即可 

     這裏種數可以取模後用hash或者直接map處理 同時處理一個bitset  f[i][j]==1 表示經過j的最短路種數爲i 

代碼(bzoj上被卡內存? loj上過了)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int mod=1000007;
const int maxn=110000;
struct {
	int x,y,z;
}e[maxn];
int x,y,z,len1,head[maxn],vis[maxn],next1[maxn],zhi[maxn],to[maxn],d[maxn],n,m,S,T,tot,tot2,tot3,cnt;
int  head2[maxn],next2[maxn],to2[maxn],head3[maxn],next3[maxn],to3[maxn];
int du[maxn],du1[maxn],f1[maxn],f2[maxn],dis[maxn];
map<int,int> mp;
bitset<51000> b[51000],ff[51000];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
queue<int>q1,q2;
int ans;
inline int read()
{   int x=0,f=1;  char ch;
    ch=getchar();
    while (ch>'9' || ch<'0') {  if (ch=='-') f=-1; ch=getchar(); }
	while (ch<='9'&& ch>='0') {  x=x*10+ch-'0'; ch=getchar(); }
	return f*x; 
}
void add(int x,int y,int z)
{
    next1[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    zhi[tot]=z;
}
void add1(int x,int y)
{
    next2[++tot2]=head2[x];
    head2[x]=tot2;
    to2[tot2]=y;
}
void add2(int x,int y)
{
    next3[++tot3]=head3[x];
    head3[x]=tot3;
    to3[tot3]=y;
}
void dij()
{
	memset(d,0x3f,sizeof(d));
	d[S]=0; vis[S]=1;
	q.push(make_pair(d[S],S));
   	while (!q.empty())
	{
	  int k=q.top().first; int x=q.top().second; q.pop();
	   if (vis[x])
	   {
	   	  for (int i=head[x];i;i=next1[i])
	   	  {
	   	  	int v=to[i];
	   	  	if (d[v]>d[x]+zhi[i]) { d[v]=d[x]+zhi[i]; if (vis[v]==0) vis[v]=1,q.push(make_pair(d[v],v));} 
	   	  }
	   	  vis[x]=0;
	   }
	}
}
void bfs(int x)
{
	f1[x]=1; du[x]=0; q1.push(x);
	while (!q1.empty())
	{
		 
		int x=q1.front();q1.pop(); 
	for (int i=head2[x];i;i=next2[i])
	     {
	     	int v=to2[i]; 
	      du[v]--; f1[v]+=f1[x],f1[v]%=mod; //f1起點到每個點條數 
	     	if (du[v]==0) { q1.push(v);dis[v]=dis[x]+1; }
	     }
    }
}
void bfs1(int x1)
{
	f2[x1]=1; du1[x1]=0; q2.push(x1);
	while (!q2.empty())
	{
		int x=q2.front(); q2.pop(); b[x][x]=1; 
    	for (int i=head3[x];i;i=next3[i])
	     {
	     	int v=to3[i];  b[v]|=b[x]; 
	     	du1[v]--; f2[v]+=f2[x],f2[v]%=mod; //f1起點到每個點條數 
	     	if (du1[v]==0) { q2.push(v); }
	     }
    }
}
signed main()
{ 
   n=read(); m=read(); S=read();T=read();
   for (int i=1;i<=m;i++)
   {
   	  x=read();y=read();z=read();
   	  e[i].x=x; e[i].y=y; e[i].z=z;
      add(x,y,z); add(y,x,z);
   }
   dij();
  for (int i=1;i<=m;i++)
   {
   	    if (d[e[i].y]-d[e[i].x]==e[i].z) add1(e[i].x,e[i].y),du[e[i].y]++,add2(e[i].y,e[i].x),du1[e[i].y]++;
      	else if (d[e[i].x]-d[e[i].y]==e[i].z) add1(e[i].y,e[i].x),du[e[i].x]++,add2(e[i].x,e[i].y),du1[e[i].x]++;
   }
   bfs(S);
   bfs1(T);
   if (f1[T]==0)  {  cout<<n*(n-1)/2<<endl; return 0; }
   else
   {
   	for (int i=1;i<=n;i++)
   {
   	  if (mp[f1[i]*f2[i]%mod]==0) mp[f1[i]*f2[i]%mod]=++cnt;
   	  ff[mp[f1[i]*f2[i]%mod]][i]=1;
   }
   for (int i=1;i<=n;i++)
   {
   	  int j=mp[(mod+(f1[T]-f1[i]*f2[i])%mod)%mod];//對於每一種可能的 f1[T]-f1[i]*f2[i]中i的貢獻單獨計算,之後這個值中的i不存在 
	  ans+=((~b[i])&ff[j]).count();
   	  ff[mp[(f1[i]*f2[i])%mod]][i]=0;
   }
    cout<<(ans%mod+mod)%mod<<endl;
   }
} 

T6也挺好玩噠 

題意:n<=500000個數字,問有多少個區間的衆數出現次數嚴格大於區間長度的一半。

 一種思路:首先明確一性質 每一個區間裏只有嚴格一個衆數  所以我們可以對於每個衆數來計算貢獻

這時候對於每個區間重要的只有這個枚舉的數的出現次數

用一個常用思路

將序列中所有等於這個值的位置標爲1,其餘位置標爲-1。那麼如果一段區間的和大於0,它就是合法區間。用樹狀數組優化
顯然這樣維護的時間複雜度是o(cnt*n*logn)的

考慮怎麼優化,我們轉換一下題意:直接看大神的題解

現在我們要一次性處理紅色括號內的-1對答案的貢獻。也就是區間右端點R在這些-1裏的時候,有多少個合法的左端點L。

根據數字出現位置,假設它出現了k次,則可以將序列劃分成k+1段遞減的等差數列,顯然同一段等差數列之間不會有任何貢獻。

所以我們把問題轉換成兩步

1、算這一段連續區間的貢獻

假設到紅括號之前爲止,前面的數前綴和爲sum。那麼對於第一個-1,紅括號之前有多少個sum[L-1]屬於(-oo,sum-2],它就對答案有多少貢獻;對於第二個-1,紅括號之前有多少個sum[L-1]屬於(-oo,sum-3],它就對答案有多少貢獻(爲什麼不算進第一個-1,因爲很明顯左端點L不可能取這個-1);依此類推……。

2、用這一段連續區間更新樹狀數組

//同理可自己想

於是我們變成了在一段區間內加減一段等差數列+相等數列,以及查詢和操作,自然可以用線段樹維護

不過這裏要講到的是一種巧妙用樹狀數組維護的方法

我們用a[k]表示滿足(2Si)-i<=k的個數 樹狀數組f[i]維護a[i]

我們考慮樹狀數組怎麼維護區間加同一個數:見鏈接

而由於本題加的數列特殊性 可以轉變爲+(一段等差數列-一段等差數列)

區間維護+等差數列相當於上述鏈接做二次差分

代碼[學習來的]

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int maxn=1100000;
int next1[maxn],head[maxn],a[maxn],fa[maxn],fb[maxn],fc[maxn],ans,n,type,lim,p,w[maxn],vis[maxn];
void add(int x,int p)
{
	for (int i=x;i<=lim;i+=i&(-i))
	{
		fa[i]+=p; fb[i]+=x*p; fc[i]+=(x-1)*(x-2)*p;
	}
}
int ask(int x)
{
	int a=0,b=0,c=0;
	for (int i=x;i>0;i-=i&(-i))
	{
		a+=fa[i],b+=fb[i],c+=fc[i];
	}
	return (x*x+3*x)*a-2*b*x+c; 
}
void work(int l,int r,int zhi,int t)
{
	int l1,r1;
	l1=zhi-(r-l)+n+1; 
	r1=zhi+n+1;
	if (t==-1)  {add(r1+1,1);add(l1,-1);} 
	else { 
	        ans+=ask(r1-1)-ask(l1-2);//嚴格小於
            add(r1+1,-1);add(l1,1);
	     } 
}
signed  main()
{
	scanf("%lld%lld",&n,&type);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		next1[i]=head[a[i]];
		head[a[i]]=i;
	}
	lim=n*2;
	for (int i=n;i>=1;i--)
	{
		if (!vis[a[i]])
		{
			p=0; w[0]=n+1; 
			for (int j=head[a[i]];j;j=next1[j]) w[++p]=j;
			w[++p]=0; 
		    for (int j=p;j;j--)//處理 
	        {
	        	work(w[j],w[j-1]-1,2*(p-j+1)-w[j],1);
			}
			for (int j=p;j;j--)//清空 
	        {
	        	work(w[j],w[j-1]-1,2*(p-j+1)-w[j],-1);
			}
			vis[a[i]]=1;
		}
	}
	cout<<ans/2<<endl; 
}

 

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