CF213C Relay Race
从左上角走到右下角再回来相当于两个人同时从左上角向右下角走,由于那么我们就可以写出dp,设 f [ i ] [ j ] [ a ] [ b ] f[i][j][a][b] f[i][j][a][b]表示两个人分别走到了 (i,j) 和 (a,b) 的时候的价值和 ,因为 i+j=a+b , 所以可以少记一维,进行转移即可
P2160 [SHOI2007]书柜的尺寸
我们把书按照高度从高到低进行排序,设dp表示 f [ i ] [ a ] [ b ] [ c ] f[i][a][b][c] f[i][a][b][c]表示前 i 本书,三层宽度分别为 a b c的时候三层的高度和,因为a+b+c是前 i 本书的宽度和,所以c这一维可以省去,转移即可
P2157 [SDOI2009]学校食堂
显然(a or b) - (a and b) = (a xor b)
观察数据范围发现bi很小,可以进行状压,而且我们转移需要知道上一个是哪道菜
设
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示已经做好了前 i 个菜,且 i - i+7 的状态是 j ,上一道菜是 i+k 的最小时间
k+8作为偏移量即可转移
CF671D Roads in Yusland
因为路径都是祖先关系的,提示我们可以尝试从儿子节点向上计算
计算每种完全覆盖了u子树内的边和u到其父亲边方案,这里我们不能只保留最小值 f,因为可能有的方案覆盖上方的更多,会在以后的转移中更优秀,所以使用一个可并堆去维护
如果一个方案向上延申的不够,我们就把它从堆中删掉
否则就贡献给父亲节点,权值加上其他儿子的 f 值,减去自己的 f 值
ybtoj631. 「左偏树」次短路径
对于这种图上的问题比较难解决,可以尝试转化到树上
先建立最短路树,那么就是限制每个人不能走树上到父亲的边,可以走非树边,问到根节点的最短距离
那么对于点 u ,它的路径一定是这样的:
1.先从u走到u子树内的一点v(包含u)
2.从v走非树边到达w
3.从w走到根
那么考虑我们求出 u 的最短距离需要维护的信息,就是找打这样的点对(v,w)一个在子树内,一个在子树外,如果每个非树边维护一个disv+disw+边权,那么每个点的答案为堆顶-disu
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=4e5+5;
const int inf=0x3f3f3f3f;
int n,m;
vector <PII> G[maxn];
vector <int> T[maxn];
int dis[maxn],vis[maxn];
void dijkstra()
{
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
priority_queue <PII> q;
q.push(make_pair(0,1));
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(auto to:G[u])
{
int v=to.first;
if(dis[v]>dis[u]+to.second)
{
dis[v]=dis[u]+to.second;
q.push(make_pair(-dis[v],v));
}
}
}
}
int ex[maxn],ey[maxn],ez[maxn];
int dep[maxn],fa[maxn][21];
void dfs(int u,int f)
{
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<=20;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto to:T[u])
{
if(to==f) continue;
dfs(to,u);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[fa[x][i]]>=dep[y])
x=fa[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
vector <int> modify[maxn],del[maxn];
struct tree
{
int l,r,dis;
int val;
}tr[maxn];
int de[maxn];
int tot,rt[maxn];
int merge(int x,int y)
{
if(!x || !y) return x+y;
if(tr[x].val>tr[y].val) swap(x,y);
tr[x].r=merge(tr[x].r,y);
if(tr[tr[x].r].dis>tr[tr[x].l].dis) swap(tr[x].l,tr[x].r);
tr[x].dis=tr[tr[x].r].dis+1;
return x;
}
int ans[maxn];
void ddfs(int u,int f)
{
for(auto to:T[u])
{
if(to==f) continue;
ddfs(to,u);
rt[u]=merge(rt[u],rt[to]);
}
for(auto to:modify[u]) rt[u]=merge(rt[u],to);
for(auto to:del[u]) de[to]=1;
while(rt[u] && de[rt[u]]) rt[u]=merge(tr[rt[u]].l,tr[rt[u]].r);
if(!rt[u]) ans[u]=-1;
else ans[u]=tr[rt[u]].val-dis[u];
}
int main()
{
freopen("pal.in","r",stdin);
freopen("pal.out","w",stdout);
scanf("%d%d",&n,&m);
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
G[x].push_back(make_pair(y,z));
G[y].push_back(make_pair(x,z));
ex[i]=x, ey[i]=y; ez[i]=z;
}
dijkstra();
for(int i=1;i<=m;i++)
{
if(dis[ex[i]]==inf && dis[ey[i]]==inf) continue;
if(dis[ex[i]]==dis[ey[i]]+ez[i]) T[ey[i]].push_back(ex[i]);
if(dis[ey[i]]==dis[ex[i]]+ez[i]) T[ex[i]].push_back(ey[i]);
}
dfs(1,0);
for(int i=1;i<=m;i++)
{
if(dis[ex[i]]==inf && dis[ey[i]]==inf) continue;
if(dis[ex[i]]!=dis[ey[i]]+ez[i] && dis[ey[i]]!=dis[ex[i]]+ez[i])
{
x=ex[i]; y=ey[i]; z=lca(x,y);
modify[x].push_back(++tot);
tr[tot].dis=0; tr[tot].val=dis[x]+dis[y]+ez[i];
del[z].push_back(tot);
modify[y].push_back(++tot);
tr[tot].dis=0; tr[tot].val=dis[x]+dis[y]+ez[i];
del[z].push_back(tot);
}
}
ddfs(1,0);
for(int i=2;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}
ybtoj601. 「强连通分量」判二分图
先匈牙利判断一下最大匹配是否为n,顺便解出一组解
然后对于有匹配的我们左连右,对于没匹配的边我们右连左
如果一个未被匹配的边在一个强连通分量内,就可以成为匹配的边
因为可以构成类似交错增广路的那种
注意n<m,所以我们需要建立一个虚拟节点
把右侧多的点都连到虚拟节点,保证连通性
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3005;
int n,m;
int a[305][1505];
int vis[maxn],T;
int p[maxn];
bool dfs(int x)
{
for(int i=n+1;i<=n+m;i++)
{
if(!a[x][i] || vis[i]==T) continue;
vis[i]=T;
if(!p[i] || dfs(p[i]))
{
p[i]=x;
return 1;
}
}
return 0;
}
vector <int> G[maxn];
int dfn[maxn],low[maxn],dfstime;
int st[maxn],top,in[maxn];
int col[maxn],cnt;
void tarjan(int u)
{
dfn[u]=low[u]=++dfstime;
st[++top]=u; in[u]=1;
for(auto to:G[u])
{
if(!dfn[to])
{
tarjan(to);
low[u]=min(low[u],low[to]);
}
else if(in[to])
low[u]=min(low[u],dfn[to]);
}
if(low[u]==dfn[u])
{
in[u]=0; col[u]=++cnt;
while(st[top]!=u)
{
in[st[top]]=0;
col[st[top]]=cnt;
--top;
}
--top;
}
}
int main()
{
freopen("fantasy.in","r",stdin);
freopen("fantasy.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x; scanf("%1d",&x);
if(x) a[i][j+n]=x;
}
int ans=0;
for(int i=1;i<=n;i++)
{
++T;
if(dfs(i)) ans++;
}
if(ans<n)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
printf("1");
printf("\n");
}
return 0;
}
for(int i=1;i<=n;i++)
for(int j=n+1;j<=n+m;j++)
{
if(!a[i][j]) continue;
if(p[j]==i) G[i].push_back(j);
else G[j].push_back(i);
}
for(int i=n+1;i<=n+m;i++)
{
if(p[i]) G[i].push_back(n+m+1);
else G[n+m+1].push_back(i);
}
for(int i=1;i<=n+m+1;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
{
for(int j=n+1;j<=n+m;j++)
if(a[i][j] && (p[j]==i || col[i]==col[j]))
printf("0");
else printf("1");
printf("\n");
}
return 0;
}
ybtoj591. 「费用流」愉悦程度
问题难以直接建模,考虑使用解方程的方式计算
列出不等式,加一个delta转化成等式,等式两侧作为流量入和出的守恒建边,先全部假设是睡觉,边权改为吃饭-睡觉用于调整
具体的建边方式见代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1010;
const int inf=0x3f3f3f3f;
int n,k,SS,EE;
int head[maxn],tot=1;
struct edge
{
int to,nxt;
ll v,c;
}e[maxn<<4];
void add(int x,int y,ll z,ll val)
{
e[++tot].to=y; e[tot].nxt=head[x]; e[tot].v=z; e[tot].c=-val; head[x]=tot;
e[++tot].to=x; e[tot].nxt=head[y]; e[tot].v=0; e[tot].c=val; head[y]=tot;
}
int vals[maxn],vale[maxn];
int S,T,vis[maxn],pre[maxn];
ll dis[maxn];
bool spfa()
{
queue <int> q;
for(int i=S;i<=T;i++) vis[i]=0,dis[i]=1e17;
dis[S]=0; q.push(S); vis[S]=1;
while(!q.empty())
{
int u=q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int to=e[i].to;
if(e[i].v && dis[to]>dis[u]+e[i].c)
{
dis[to]=dis[u]+e[i].c;
pre[to]=i;
if(!vis[to]) q.push(to),vis[to]=1;
}
}
}
return dis[T]!=1e17;
}
ll mcmf()
{
ll res=0;
while(spfa())
{
ll flow=inf;
for(int i=T;i!=S;i=e[pre[i]^1].to) flow=min(flow,e[pre[i]].v);
for(int i=T;i!=S;i=e[pre[i]^1].to) e[pre[i]].v-=flow,e[pre[i]^1].v+=flow;
res+=flow*dis[T];
}
return res;
}
int main()
{
freopen("delight.in","r",stdin);
freopen("delight.out","w",stdout);
scanf("%d%d%d%d",&n,&k,&SS,&EE);
ll ans=0;
for(int i=1;i<=n;i++) scanf("%d",&vals[i]),ans+=vals[i];
for(int i=1;i<=n;i++) scanf("%d",&vale[i]);
add(0,1,k-SS,0); add(1,2,k-SS-EE,0);
for(int i=1;i<=k;i++) add(1,i+1,1,vale[i]-vals[i]);
for(int i=1;i<n;i++) add(i+1,i+2,k-SS-EE,0);
for(int i=1;i+k<=n;i++) add(i+1,i+k+1,1,vale[k+i]-vals[k+i]);
for(int i=n-k+1;i<=n;i++) add(i+1,n+2,inf,0);
S=0; T=n+2;
printf("%lld\n",ans-mcmf());
return 0;
}
ybtoj571. 「二分图匹配」炸弹游戏
如果不存在硬石头,我们可以把每个可以放炸弹的位置(x,y),x->y+n连上边,求二分图最大匹配
考虑有硬石头,把横着的一段不存在硬石头和竖着的一段不存在硬石头的段分别作为二分图左侧和右侧,按照连通块编号计算即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
char s[55][55];
int vis[5001],bl[5001];
int tot,idx[55][55],idy[55][55];
vector <int> G[5001];
void add(int x,int y)
{
G[x].push_back(y);
}
int dfs(int u)
{
for(auto to:G[u])
{
if(!vis[to])
{
vis[to]=1;
if(!bl[to] || dfs(bl[to]))
{
bl[to]=u;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("bomb.in","r",stdin);
freopen("bomb.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(s[i][j]=='#') continue;
if(j==1 || s[i][j-1]=='#') tot++;
idx[i][j]=tot;
}
}
for(int j=1;j<=m;j++)
for(int i=1;i<=n;i++)
{
if(s[i][j]=='#') continue;
if(i==1 || s[i-1][j]=='#') tot++;
idy[i][j]=tot;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='*')
add(idx[i][j],idy[i][j]);
int res=0;
for(int i=1;i<=tot;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i)) res++;
}
printf("%d\n",res);
return 0;
}
ybtoj582. 「网络流」大收藏家
S连每个人容量为1,表示每个人有一种宝物可以产生贡献,第一个人连T容量为a1
然后拆点,每层只需要保留有用的节点,每个人拆开的点,可以向自己的下一个状态保留ai,表示自己的最大容量
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=30005;
int TT,n,m;
int head[maxn],S,T,tot;
struct edge
{
int to,nxt,v;
}e[maxn<<4];
void add(int x,int y,int z)
{
// printf("%d %d %d\n",x,y,z);
e[++tot].to=y; e[tot].nxt=head[x]; e[tot].v=z; head[x]=tot;
e[++tot].to=x; e[tot].nxt=head[y]; e[tot].v=0; head[y]=tot;
}
int bh,a[maxn];
map <int,int> id[maxn];
int maxflow;
int dep[maxn];
bool bfs()
{
queue <int> q;
q.push(S);
for(int i=S;i<=T;i++) dep[i]=-1;
dep[S]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int to=e[i].to;
if(e[i].v && dep[to]==-1)
{
dep[to]=dep[u]+1;
q.push(to);
}
}
}
return dep[T]!=-1;
}
int dfs(int u,int flow)
{
if(u==T) return flow;
int res=0;
for(int i=head[u];i;i=e[i].nxt)
{
int to=e[i].to;
if(dep[to]==dep[u]+1 && e[i].v)
{
int tmp=dfs(to,min(flow,e[i].v));
e[i].v-=tmp; e[i^1].v+=tmp;
res+=tmp; flow-=tmp;
}
}
if(!res) dep[u]=-1;
return res;
}
void dinic()
{
maxflow=0;
while(bfs())
{
maxflow+=dfs(S,inf);
}
return ;
}
int main()
{
freopen("collection.in","r",stdin);
freopen("collection.out","w",stdout);
scanf("%d",&TT);
while(TT--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<=bh+1;i++) head[i]=0;
tot=1; bh=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) id[i].clear(),id[i][1]=++bh;
int x,y;
scanf("%d%d",&x,&y); add(id[x][1],id[y][1],1),add(id[y][1],id[x][1],1);
for(int i=2;i<=m;i++)
{
scanf("%d%d",&x,&y);
id[x][i]=++bh; id[y][i]=++bh;
add(bh-1,bh,1); add(bh,bh-1,1);
}
for(int i=1;i<=n;i++)
{
int lst=0;
map <int,int> &s=id[i];
for(map <int,int>::iterator it=s.begin();it!=s.end();it++)
{
if(!lst)
add(S,it->second,1);
else add(lst,it->second,a[i]);
lst=it->second;
}
}
T=bh+1;
add((--id[1].end())->second,T,a[1]);
dinic();
printf("%d\n",maxflow);
}
return 0;
}
本文解析了多项经典算法题目,包括动态规划、最短路径、网络流等,通过具体实例展示了算法设计与实现过程。
1469

被折叠的 条评论
为什么被折叠?



