似乎找不到什麼雞湯來安慰那個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;
}