我的博客大概要封筆了,最后一周也不會做什么題了,再見了朋友們。
[HNOI2014] 道路堵塞
題目描述
解法
我們不妨考慮增量法,先把在最短路徑上的邊排除掉,跑完最短路之后再慢慢添加邊。
如果我們要求刪除邊 \(i\) 的答案,那么我們需要添加邊 \([1,i)\),並且考慮 \((i,k]\) 邊的影響(這些邊我們是不加的),考慮把 \((i,k]\) 構成的路徑染色,那么如果我們到達的某個點被染色,那么可以直接走最短路到終點。
為了保證復雜度我們把給定的最短路徑染色,如果現在 \(\tt spfa\) 更新到了最短路上的第 \(i\) 個點,那么我們這條路徑打上時間戳 \(i\),如果刪除的邊 \(\in(i,k]\),那么這條拼湊出來的路徑是對答案有貢獻的,用一個堆維護即可。
時間復雜度基於 \(\tt spfa\),所以在不刻意卡的情況是可以通過的。
還有一種時間復雜度穩定的最短路樹做法,找機會填坑。
總結
圖論中的一些動態算法十分重要,巧用動態算法可以快速完成版本之間的轉化,以解決一維偏序關系。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,tot,f[M],id[M],d[M],in[M];
queue<int> q;int rd[M],p[M],g[M],ban[M];
struct edge{int v,c,next;}e[M];
struct node
{
int u,c;
bool operator < (const node &b) const
{return c>b.c;}
};priority_queue<node> s;
void spfa(int now)
{
q.push(now);in[now]=1;
while(!q.empty())
{
int u=q.front();q.pop();in[u]=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(ban[i]) continue;
if(d[v]>d[u]+c)
{
d[v]=d[u]+c;
if(id[v]) s.push({id[v],d[v]+g[id[v]]});
else if(!in[v]) in[v]=1,q.push(v);
}
}
}
}
signed main()
{
n=read();m=read();k=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read();
e[++tot]=edge{v,c,f[u]},f[u]=tot;
}
for(int i=1;i<=k;i++)
{
rd[i]=read();ban[rd[i]]=1;
p[i+1]=e[rd[i]].v;id[p[i+1]]=i+1;
}
p[1]=1;id[1]=1;
for(int i=k;i>=1;i--) g[i]=g[i+1]+e[rd[i]].c;
memset(d,0x3f,sizeof d);
d[1]=0;spfa(1);
for(int i=1;i<=k;i++)
{
while(!s.empty() && s.top().u<=i) s.pop();
if(s.empty()) puts("-1");
else printf("%d\n",s.top().c);
d[p[i+1]]=d[p[i]]+e[rd[i]].c;
spfa(p[i+1]);
}
}
[CQOI2017] 小Q的表格
題目描述
解法
用了一個多小時手切了這道題,雖然是黑題但是聽說特別簡單,沒啥成就感。
首先思考什么節點會被影響到,觀察 \(f\) 下標的形式是 \((a,b)\rightarrow (a,a+b)\) 的形式,但凡帶點腦子的都可以想到輾轉相除法,所以可以給出關鍵 \(\tt observation\):修改 \((x,y)\) 影響且只會影響到 \(\gcd(x,y)=\gcd(a,b)\) 的點 \((a,b)\)
並且修改的影響是相對於初始值整體乘上一個倍數 \(z\)(注意 \(z\) 不一定是整數),考慮按 \(\tt gcd\) 種類分類算貢獻,那么問題轉化成求出 \(k\) 正方形范圍內,\(\gcd(a,b)=x\) 的 \(a\cdot b\) 之和,直接開始推式子:
那么單次求是 \(O(\sqrt n)\) 的整除分塊,可以把這東西記憶化,時間復雜度 \(O(m^2+n\sqrt n)\),可以跑過省選的原數據,但是在 \(\tt luogu\) 上被卡了。正確的思路是把所有 \(\tt gcd\) 混在一起推式子,也不是特別難。
#include <cstdio>
#include <cassert>
const int N = 10005;
const int M = 4000005;
const int MOD = 1e9+7;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int m,n,cnt,p[M],vis[M],mu[M],s2[M];
int t,a[N],b[N],f[M],s[M];
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int sum(int x) {return x*(x+1)/2%MOD;}
int sqr(int x) {return x*x%MOD;}
void init(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i]) p[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt && i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0) break;
mu[i*p[j]]=-mu[i];
}
}
for(int i=1;i<=n;i++)
s2[i]=(s2[i-1]+mu[i]*i*i)%MOD;
for(int i=1;i<=n;i++)
s[i]=(s[i-1]+i*(sum(i)+sum(i-1)))%MOD;
}
int get(int n)
{
if(f[n]) return f[n];
int res=0;
for(int l=1,r=1;l<=n;l=r+1)
{
r=n/(n/l);
res=(res+(s2[r]-s2[l-1])*sqr(sum(n/l)))%MOD;
}
return f[n]=res;
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void work()
{
int A=read(),B=read(),x=read(),k=read();
int y=gcd(A,B),fl=0,ans=s[k];
x=x%MOD*qkpow(A*B%MOD,MOD-2)%MOD;
for(int i=1;i<=t;i++) if(a[i]==y)
{fl=1;b[i]=x;break;}
if(!fl) a[++t]=y,b[t]=x;
for(int i=1;i<=t;i++)
ans=(ans+(b[i]-1)*get(k/a[i])
%MOD*sqr(a[i]))%MOD;
printf("%lld\n",(ans+MOD)%MOD);
}
signed main()
{
m=read();n=read();init(n);
for(int i=1;i<=m;i++) work();
}
[CQOI2017] 老C的方塊
題目描述
解法
一開始的結論假了,導致做法也假了,實際上還是題目沒有讀清楚。
根據套路,我們嘗試把限制表達成路徑,然后跑最小割來解決。
首先要確定路徑的形式,我們先猜想路徑存在一種簡單的統一形式,發現可以染色的方式給出構造:

那么路徑一定是 \(1\rightarrow 2\rightarrow 3\rightarrow 4\) 的形式,那么可以建立一個分層圖,如果兩點相鄰並且層也相鄰,就把他們連起來。因為割的是點權,所以還需要拆點以轉化為邊權。
#include <cstdio>
#include <iostream>
#include <unordered_map>
#include <queue>
using namespace std;
const int M = 100005;
const int N = 200005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,a[M],b[M],c[M],w[M],S,T,tot,f[N],cur[N],d[N];
int dx[4]={1,-1},dy[4]={0,0,1,-1};
unordered_map<int,int> mp[M];
struct edge{int v,c,next;}e[10*M];
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
queue<int> q;q.push(S);d[S]=1;
for(int i=1;i<=T;i++) d[i]=0;
while(!q.empty())
{
int u=q.front();q.pop();
if(u==T) return 1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!d[v] && e[i].c>0)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int ept)
{
if(u==T) return ept;
int tmp=0,flow=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(d[v]==d[u]+1 && e[i].c>0)
{
tmp=dfs(v,min(ept,e[i].c));
if(!tmp) continue;
ept-=tmp;flow+=tmp;
e[i].c-=tmp;e[i^1].c+=tmp;
if(!ept) break;
}
}
return flow;
}
signed main()
{
read();read();n=read();S=0;T=2*n+2;tot=1;
for(int i=1;i<=n;i++)
{
b[i]=read();a[i]=read();w[i]=read();
mp[a[i]][b[i]]=i;
if(a[i]&1) c[i]=4-(b[i]%4);
else c[i]=(b[i]%4+2)%4+1;
add(i<<1,i<<1|1,w[i]);
if(c[i]==1) add(S,i<<1,inf);
if(c[i]==4) add(i<<1|1,T,inf);
}
for(int i=1;i<=n;i++) for(int j=0;j<4;j++)
{
int x=a[i]+dx[j],y=b[i]+dy[j];
if(x<=0 || y<=0 || !mp[x].count(y)) continue;
int to=mp[x][y];
if(c[i]+1==c[to]) add(i<<1|1,to<<1,inf);
}
int ans=0;
while(bfs())
{
for(int i=S;i<=T;i++) cur[i]=f[i];
ans+=dfs(S,inf);
}
printf("%d\n",ans);
}
[JSOI2009] 等差數列
題目描述
解法
沒想到啊,等差數列轉差分具有十分簡潔的形式,設 \(c_i=a_{i+1}-a_i\),我們維護一個長度為 \(n-1\) 的差分數組 \(c\),那么每次修改相當於兩個單點修改,加上一個區間修改。
考慮詢問,發現等差數列一定是 \(c\) 連續相同的一段,若 \(\forall i\in[l,r],c_i=c_l\),則這個等差數列可以覆蓋點 \([l,r+1]\),我們需要找到最少的等差數列使得所有點都被覆蓋,這里有一個誤區就是答案並不等於 \(c\) 相同連續段的個數。
考慮把它放在線段樹上維護,設 \(s[0/1/2/3]\) 分別表示 左右端點都不被覆蓋 \(/\) 左端點被覆蓋 \(/\) 右端點被覆蓋 \(/\) 左右端點都被覆蓋 的最小等差數列數。合並的時候需要保證中間的點被左邊或者右邊覆蓋才行,如果被兩邊都覆蓋了,那么可以考慮判斷合並兩個等差數列。
時間復雜度 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[M];
struct node {int l,r,fl,s[4];}t[M<<2];
void upd(int &x,int y) {x=min(x,y);}
node operator + (node a,node b)
{
node w;w.l=a.l;w.r=b.r;w.fl=0;
int z=a.r==b.l;
w.s[0]=a.s[2]+b.s[1]-z;
upd(w.s[0],a.s[2]+b.s[0]);
upd(w.s[0],a.s[0]+b.s[1]);
//
w.s[1]=a.s[3]+b.s[1]-z;
upd(w.s[1],a.s[3]+b.s[0]);
upd(w.s[1],a.s[1]+b.s[1]);
//
w.s[2]=a.s[2]+b.s[3]-z;
upd(w.s[2],a.s[2]+b.s[2]);
upd(w.s[2],a.s[0]+b.s[3]);
//
w.s[3]=a.s[3]+b.s[3]-z;
upd(w.s[3],a.s[3]+b.s[2]);
upd(w.s[3],a.s[1]+b.s[3]);
return w;
}
void fuck(int i,int c)
{
if(!i) return ;
t[i].l+=c;t[i].r+=c;t[i].fl+=c;
}
void down(int i)
{
if(!t[i].fl) return ;
fuck(i<<1,t[i].fl);
fuck(i<<1|1,t[i].fl);t[i].fl=0;
}
void build(int i,int l,int r)
{
if(l==r)
{
t[i].l=t[i].r=a[l];
t[i].s[1]=t[i].s[2]=t[i].s[3]=1;
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
t[i]=t[i<<1]+t[i<<1|1];
}
void ins(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {fuck(i,c);return ;}
int mid=(l+r)>>1;down(i);
ins(i<<1,l,mid,L,R,c);
ins(i<<1|1,mid+1,r,L,R,c);
t[i]=t[i<<1]+t[i<<1|1];
}
node ask(int i,int l,int r,int L,int R)
{
if(L<=l && r<=R) return t[i];
int mid=(l+r)>>1;down(i);
if(mid<L) return ask(i<<1|1,mid+1,r,L,R);
if(R<=mid) return ask(i<<1,l,mid,L,R);
return ask(i<<1,l,mid,L,R)
+ask(i<<1|1,mid+1,r,L,R);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++) a[i]=a[i+1]-a[i];
build(1,1,n);
m=read();char s[4];
while(m--)
{
scanf("%s",s);int l=read(),r=read();
if(s[0]=='A')
{
int a=read(),b=read();
if(l>1) ins(1,1,n,l-1,l-1,a);
if(r<n) ins(1,1,n,r,r,-a-b*(r-l));
if(l^r) ins(1,1,n,l,r-1,b);
}
if(s[0]=='B')
{
if(l==r) {puts("1");continue;}
printf("%lld\n",ask(1,1,n,l,r-1).s[3]);
}
}
}
