6.最长不下降子序列问题
思路:
第一问:
直接LIS就能求出来最大长度ma
第二问:
因为每个数只能用一次,把一个点拆成入点和出点,入点连出点,容量为1,这样限制使用次数。
源点连接每个dp[i]=1 的点的入点(即序列起点),dp[i]=ma的点的出点连接汇点(即序列终点)
然后如果对于a[i]<=a[j]且dp[i]+1==dp[j]的两个数,i的出点连接j的入点,容量为1
源点连接每个入点容量为,每个出点连接汇点容量为1,
最大流即为能取出的长度为ma的子序列个数
!注意有一个坑点就是ma可能等于1,所以可能即连接源点也可能连接汇点,判断要用两个if而不能用ifelse
第三问:
把和a1与an有关的边权设置为inf就行了
直接再残余网络上加边,新跑出来的流是原来的最大流基础上的增加量,加上原来的最大流即为答案
!注意这里还有一个坑点就是如果ma==1,按第二问的解决方法应该也连接汇点,
但如果同时连接起点到汇点就无限了,
所以点1不能同时连接源点和汇点,同理点n也不能同时连接源点和汇点
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int m,n;
int s,t;
int maxflow;
//这题需要的数组:
int a[maxm];//存数
int dp[maxm];//dp数组
int ma;//最大长度
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
memset(d,0,sizeof d);
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(!d[v]&&w[i]){
d[v]=d[x]+1;
if(v==t)return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int x,int flow){
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
if(k){
w[i]-=k;
w[i^1]+=k;
res-=k;
}else{
d[v]=-1;
}
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
signed main(){
init();
/*
1.计算最长不下降子序列长度ma
2.计算最多可以取出多少个长度为ma的不下降子序列
3.如果允许多次使用x1和xn,最多可以取出多少长度为s的不下降子序列
*/
scanf("%d",&n);
for(int i=1;i<=n;i++){//input
scanf("%d",&a[i]);
}
//下面处理第一问
for(int i=1;i<=n;i++){//求最大不下降子序列长度
dp[i]=1;
for(int j=1;j<i;j++){
if(a[j]<=a[i]){//不下降
dp[i]=max(dp[i],dp[j]+1);
}
}
ma=max(ma,dp[i]);
}
printf("%d\n",ma);//第一问
//下面处理第二问
/*
每个点拆成入点i和出点i+n
源点0
汇点n+n+1
*/
s=0,t=n+n+1;
for(int i=1;i<=n;i++){//入点连接出点,容量为1,用来限制每个点只能用一次
add(i,i+n,1);
add(i+n,i,0);
}
for(int i=1;i<=n;i++){
if(dp[i]==1){//源点连接起点入点
add(s,i,1);
add(i,s,0);
}//这里不能是else if,因为可能ma==1,恶心人
if(dp[i]==ma){//终点的出点连接汇点
add(i+n,t,1);
add(t,i+n,0);
}
for(int j=1;j<i;j++){
if(dp[j]+1==dp[i]&&a[j]<=a[i]){
add(j+n,i,1);
add(i,j+n,0);
}
}
}
dinic();
printf("%d\n",maxflow);//第二问
//下面处理第三问
int flow=maxflow;//记录一下之前的结果
//处理x1和xn可以使用无限次
add(1,1+n,inf);//修改入点到出点的边权
add(n,n+n,inf);
add(s,1,inf);//x1肯定是起点
//注意不能判断dp[1]==ma的情况,如果这样就无限了
if(dp[n]==ma){//如果xn是终点
add(n+n,t,inf);
}
//
dinic();
flow+=maxflow;
printf("%d\n",flow);//第三问
return 0;
}
//https://www.luogu.com.cn/problem/P2766
7.试题库问题
思路:
二分图多重匹配问题
也基本是模板题
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int s,t;
int maxflow;
//这题要用到的:
int n,m,k;
vector<int>ans[25];
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
memset(d,0,sizeof d);
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(!d[v]&&w[i]){
d[v]=d[x]+1;
if(v==t)return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int x,int flow){
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
if(k){
w[i]-=k;
w[i^1]+=k;
res-=k;
}else{
d[v]=-1;
}
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
signed main(){
init();
scanf("%d%d",&k,&n);
s=0,t=k+n+1;
for(int i=1;i<=n;i++){//每个物品只能用一次
add(s,i,1);
add(i,s,0);
}
for(int i=1;i<=k;i++){
int x;
scanf("%d",&x);
m+=x;//m为需要的总数量
//需要的每种类型连接汇点,容量为需要的数量
add(i+n,t,x);
add(t,i+n,0);
}
for(int i=1;i<=n;i++){
int p;
scanf("%d",&p);
while(p--){
int x;
scanf("%d",&x);
//物品i可以用于类型x
add(i,x+n,1);
add(x+n,i,0);
}
}
dinic();
if(maxflow!=m){//如果最大流不等于m,说明无解
puts("No Solution!");
}else{
for(int x=1;x<=n;x++){
for(int i=head[x];i!=-1;i=nt[i]){
if(w[i^1]){//反向边有流说明是匹配边
if(to[i]==s||to[i]==t)continue;
ans[to[i]-n].push_back(x);
break;
}
}
}
for(int i=1;i<=k;i++){
printf("%d:",i);
for(int v:ans[i]){
printf(" %d",v);
}
puts("");
}
}
return 0;
}
//https://www.luogu.com.cn/problem/P2763
9.方格取数问题
思路:
二维坐标,相邻的不能选,所以可以把方格表进行黑白染色(按横纵坐标和的奇偶性),让黑白节点形成二分图。
建图:
源点连每一个黑点,容量为格子的值;每一个白点连汇点,容量为格子的值。
每一个黑色的点都连向与它相邻的白色的点(即互斥点相连),容量为inf。
最小割就是需要舍弃的最小和。
答案=总和-最小割
code:
#include <bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int inf=1e9;
int head[maxm],nt[maxm<<3],to[maxm<<3],w[maxm<<3],cnt;
int d[maxm];
int s,t;
int maxflow;
//这题要用到的:
int dir[4][2]={1,0,-1,0,0,1,0,-1};
int g[maxm][maxm];
int m,n;
int id(int i,int j){//二维转一维
return (i-1)*n+j;
}
//
void init(){
memset(head,-1,sizeof head);
cnt=1;
}
void add(int x,int y,int z){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
memset(d,0,sizeof d);
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(!d[v]&&w[i]){
d[v]=d[x]+1;
if(v==t)return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int x,int flow){
if(x==t)return flow;
int res=flow;
for(int i=head[x];i!=-1;i=nt[i]){
int v=to[i];
if(w[i]&&d[v]==d[x]+1){
int k=dfs(v,min(res,w[i]));
if(k){
w[i]-=k;
w[i^1]+=k;
res-=k;
}else{
d[v]=-1;
}
if(!res)break;
}
}
return flow-res;
}
void dinic(){
maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
}
signed main(){
init();
scanf("%d%d",&m,&n);
s=0,t=m*n+1;
int sum=0;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%d",&g[i][j]);
sum+=g[i][j];
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if((i+j)&1){//奇数连接源点
add(s,id(i,j),g[i][j]);//权值为g[i][j]
add(id(i,j),s,0);
for(int k=0;k<4;k++){//向四个方向的排斥点连边
int x=i+dir[k][0];
int y=j+dir[k][1];
if(x<=0||x>m||y<=0||y>n)continue;
add(id(i,j),id(x,y),inf);
add(id(x,y),id(i,j),0);
}
}else{//偶数连接汇点
add(id(i,j),t,g[i][j]);//权值为g[i][j]
add(t,id(i,j),0);
}
}
}
dinic();
printf("%d\n",sum-maxflow);
return 0;
}
//https://www.luogu.com.cn/problem/P2774
10.餐巾计划问题(还没懂)
思路:
最小费用最大流
建图(摘自网络):
将一天拆成晚上和早上,每天晚上会收到脏餐巾(当天早上用完的餐巾,理解为从源点获得),每天早上又有干净的餐巾(购买、快洗店、慢洗店)。
1.从源点向每一天晚上连一条容量为当天所用餐巾x,费用为0的边,表示每天晚上从起点获得x条脏餐巾。
2.从每一天早上向汇点连一条容量为当天所用餐巾x,费用为0的边,每天白天,表示向汇点提供x条干净的餐巾,流满时表示第i天的餐巾够用 。
3.从每一天晚上向第二天晚上连一条流量为inf,费用为0的边,表示每天晚上可以将脏餐巾留到第二天晚上(注意不是早上,因为脏餐巾在早上不可以使用)。
4.从每一天晚上向这一天+快洗所用天数fa的那一天早上连一条流量为inf,费用为快洗所用钱数的边,表示每天晚上可以送去快洗部,在地i+fa天早上收到餐巾 。
5.同理,从每一天晚上向这一天+慢洗所用天数sl的那一天早上连一条流量为inf,费用为慢洗所用钱数的边,表示每天晚上可以送去慢洗部,在地i+sl天早上收到餐巾 。
6.从起点向每一天早上连一条流量为inf,费用为购买餐巾所用钱数的边,表示每天早上可以购买餐巾 。
注意3~6点需要做判断(即连向的边必须<=n)
ps:
费用和爆int,要开longlong
code:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=2e5+5;
const int inf=0x3f3f3f3f;
struct Node{
int from,to,nt,cap,flow,cost;
}e[maxm];
int head[maxm];
int cnt;
int d[maxm];
int mark[maxm];
int pre[maxm];
int s,t;
ll mincost,maxflow;//总货物数量和总花费。
//这题要用到的:
int n;//天数
int p,fa,fac,sl,slc;
//p新餐巾费用,fa快洗天数,fac快洗费用,sl慢洗天数,slc慢洗费用
int a[maxm];//每天需用的餐巾数
//
void init(){
cnt=-1;
memset(head,-1,sizeof head);
}
void add(int a,int b,int c,int d){
cnt++;
e[cnt].nt=head[a];
head[a]=cnt;
e[cnt].from=a;
e[cnt].to=b;
e[cnt].cap=c;
e[cnt].flow=0;
e[cnt].cost=d;
cnt++;
e[cnt].nt=head[b];
head[b]=cnt;
e[cnt].from=b;
e[cnt].to=a;
e[cnt].cap=0;
e[cnt].flow=0;
e[cnt].cost=-d;
}
bool spfa(){
memset(mark,0,sizeof mark);
memset(d,inf,sizeof d);
queue<int>q;
q.push(s);
d[s]=0;
mark[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
mark[x]=0;
for(int i=head[x];i!=-1;i=e[i].nt){
int v=e[i].to;
if(e[i].cap>e[i].flow&&d[v]>d[x]+e[i].cost){
d[v]=d[x]+e[i].cost;
pre[v]=i;
if(!mark[v]){
q.push(v);
mark[v]=1;
}
}
}
}
return d[t]!=inf;
}
void ek(){
mincost=maxflow=0;
while(spfa()){
int k=inf;
for(int i=t;i!=s;i=e[pre[i]].from){
k=min(k,e[pre[i]].cap-e[pre[i]].flow);
}
for(int i=t;i!=s;i=e[pre[i]].from){
e[pre[i]].flow+=k;
e[pre[i]^1].flow-=k;
}
maxflow+=k;
mincost+=k*d[t];
}
}
signed main(){
init();
scanf("%d",&n);
s=0,t=n+n+1;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
add(s,i,a[i],0);//每天晚上从起点获得a[i]条脏餐巾
add(i+n,t,a[i],0);//每天白天向汇点提供a[x]条干净餐巾
}
scanf("%d%d%d%d%d",&p,&fa,&fac,&sl,&slc);
//p新餐巾费用,fa快洗天数,fac快洗费用,sl慢洗天数,slc慢洗费用
for(int i=1;i<=n;i++){
if(i+1<=n){
add(i,i+1,inf,0);//每天晚上可以把脏餐巾留到第二天
}
if(i+fa<=n){
add(i,i+n+fa,inf,fac);//快洗
}
if(i+sl<=n){
add(i,i+n+sl,inf,slc);//慢洗
}
add(s,i+n,inf,p);//直接买新的
}
ek();
printf("%lld\n",mincost);
return 0;
}
//https://www.luogu.com.cn/problem/P1251/