2019年3月博客汇总
[NOIP2018]赛道修建
蒟蒻思路
首先由“使得修建的m条赛道中长度最小的赛道长度最大
”看出来是二分答案,于是二分的判定便为给定长度x,问是否在无根树上找出尽量多的长度大于等于x的互不相交的路径,之后与m值进行比较。
之后是怎样找出最多的路径,可以从数据范围看出这题时间复杂度为应该较低,很有可能是贪心。
我们先来康康CoCoF给的特例数据点。
菊花图
对于菊花图来说贪心策略类似于纪念品分组。即排序后双指针,将最大的路安排给最小的路。
链
对于链来说可以从左往右扫描,当当前长度大于等于m时新建一条赛道。
一条路
求直径………
其他的特殊类型还有二叉树,但因为其与正解已经类似所以不在赘述
我们发现如果树的末尾如果没用的话那是一种浪费,本着不用白不用的原则我们应该从树的叶子节点往上枚举,并将其长度累计到上层。如果在这层能拼出符合要求的道路便在这层拼好。拼每一层都用纪念品分组的贪心策略。
以上贪心看起来是对的,但其实存在重大偏差。
比如有两个方法都可以组出同样大的赛道,而一种方法剩下的较大,一种方法剩下的较小,明显选择剩下较大的方案较优。改变贪心策略从小到大枚举,用set查找即可。
最终的时间复杂度为\(O(n\log{n}\log{maxlenth})\)
Code
#include<ctime>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N=50010,M=100010;
int head[N],Next[M],ver[M],edge[M],debug[100],tol=1,n,m,start_point,cnt,min_lenth,Degree[N],*dis[N];
bool vis[N],flag;
struct node{
int x,dis;
node(int a,int b){
x=a,dis=b;
}
};
queue<node>q;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y,int z){
Next[++tol]=head[x],edge[tol]=z,ver[tol]=y,head[x]=tol;
Next[++tol]=head[y],edge[tol]=z,ver[tol]=x,head[y]=tol;
}
int getLenth(){
int t,d,dd,mx=-100000,mx_point;
start_point=rand()%n+1;
mx_point=start_point;
q.push(node(start_point,0));
vis[start_point]=true;
while(!q.empty()){
t=q.front().x;
d=q.front().dis;
q.pop();
for(int i=head[t];i;i=Next[i]){
if(vis[ver[i]])continue;
vis[ver[i]]=true;
q.push(node(ver[i],dd=d+edge[i]));
if(dd>mx){
mx=dd;
mx_point=ver[i];
}
}
}
for(int i=0;i<=n;++i)vis[i]=false;
mx=-10000;
q.push(node(mx_point,0));
vis[mx_point]=true;
while(q.size()){
t=q.front().x;
d=q.front().dis;
q.pop();
for(int i=head[t];i;i=Next[i]){
if(vis[ver[i]])continue;
q.push(node(ver[i],dd=d+edge[i]));
if(dd>mx){
mx=dd;
mx_point=ver[i];
}
vis[ver[i]]=true;
}
}
return mx;
}
int dfs(int x,int last_lenth){
int ii=0;
for(int i=head[x];i;i=Next[i]){
if(vis[ver[i]])continue;
vis[ver[i]]=true;
dis[x][ii]=dfs(ver[i],edge[i]);
if(!dis[x][ii])--ii;
++ii;
}
multiset<int>s;
multiset<int>::iterator t,tt;
int d,unuse_mx=0,dd;
for(int i=0;i<ii;++i)s.insert(dis[x][i]);
while(s.size()){
t=s.begin();
d=*t;
dd=min_lenth-d;
s.erase(t);
tt=s.lower_bound(dd);
if(tt!=s.end()){
++cnt;
if(cnt>=m){
flag=true;
throw 233;
}
s.erase(tt);
}
else{
unuse_mx=max(unuse_mx,d);
}
}
if(unuse_mx+last_lenth>=min_lenth){
++cnt;
if(cnt>=m){
flag=true;
throw 233;
}
return 0;
}
return unuse_mx+last_lenth;
}
inline bool check(int x){
start_point=rand()%n+1;
cnt=0;
min_lenth=x;
flag=false;
for(int i=0;i<=n+1;++i)vis[i]=false;
vis[start_point]=true;
try{
dfs(start_point,0);
}catch(int x){}
return flag;
}
int main(){
int x,y,z,l,r,mid;
srand(time(0));
n=read();m=read();
for(int i=1;i<n;++i){
x=read(),y=read(),z=read();
add(x,y,z);
++Degree[x],++Degree[y];
}
for(int i=1;i<=n;++i){
dis[i]=new int[Degree[i]];
}
r=getLenth();
l=0;
bool tt;
while(l<r){
mid=(l+r+1)>>1;
tt=check(mid);
if(tt)l=mid;
else r=mid-1;
}
printf("%d",l);
return 0;
}
[NOIP2013]货车运输
思路
根据题意易得货车所走的路径一定在这张图的最大生成森林上,故可以先求出最大生成森林再跑LCA。由于这一题要求最小值,所以仿照ST表的思路进行树上倍增,于是可以在LCA的同时求出最小值。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=10050,M=1000010;
int head[N],ver[M],Next[M],edge[M],n,tol,f[N],Min[N][20],c[N],
father[N][20],s[40],deep[N],ccnt=0;
bool vis[N];
inline int Log2(int x){
int l=0,r=30,mid;
while(l<r){
mid=(l+r+1)>>1;
if(s[mid]>x)r=mid-1;
else l=mid;
}
return l;
}
int ask(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int k=Log2(deep[x]-deep[y]),ans=(1<<29);
for(int i=k;i>=0;--i){
if(deep[x]-(1<<i)<deep[y])continue;
ans=min(ans,Min[x][i]);
x=father[x][i];
}
if(x==y)return ans;
k=Log2(deep[x]);
for(int i=k;i>=0;--i){
if(father[x][i]==father[y][i])continue;
ans=min(ans,min(Min[x][i],Min[y][i]));
x=father[x][i];
y=father[y][i];
}
ans=min(ans,min(Min[x][0],Min[y][0]));
return ans;
}
void dfs(int fa,int dep){
deep[fa]=dep;
int t,d;
for(int i=head[fa];i;i=Next[i]){
t=ver[i];
if(c[t])continue;
Min[t][0]=edge[i];
c[t]=ccnt;
d=Log2(dep+1)+1;
father[t][0]=fa;
for(int k=1;k<=d;++k){
father[t][k]=father[father[t][k-1]][k-1];
Min[t][k]=min(Min[father[t][k-1]][k-1],Min[t][k-1]);
}
dfs(ver[i],dep+1);
}
}
struct road{
int x,y,edge;
}a[M];
inline void makeLog(){
for(int i=0;i<=30;++i)s[i]=1<<i;
}
bool cmp(const road &a,const road &b){
return a.edge>b.edge;
}
inline void add(int x,int y,int z){
ver[++tol]=y,edge[tol]=z,Next[tol]=head[x],head[x]=tol;
}
int get(int x){
if(f[x]==x)return x;
return f[x]=get(f[x]);
}
inline bool same(int x,int y){
return get(x)==get(y);
}
inline void unite(int x,int y){
f[get(x)]=get(y);
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t<'0'||t>'9');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
int main(){
makeLog();
int n,m,q,tk,x,y;
deep[0]=-(1<<29);
n=read();m=read();
tk=Log2(n+1);
for(int i=0;i<=tk;++i)
for(int j=0;j<=n;++j)
Min[j][i]=(1<<29);
for(int i=0;i<=n;++i)f[i]=i;
for(int i=0;i<m;++i){
a[i].x=read();
a[i].y=read();
a[i].edge=read();
}
sort(a,a+m,cmp);
for(int i=0;i<m;++i){
if(same(a[i].x,a[i].y))continue;
unite(a[i].x,a[i].y);
add(a[i].x,a[i].y,a[i].edge);
add(a[i].y,a[i].x,a[i].edge);
}
for(int i=1;i<=n;++i){
if(c[i])continue;
++ccnt;
c[i]=ccnt;
dfs(i,0);
}
q=read();
for(int i=0;i<q;++i){
x=read();y=read();
if(c[x]==c[y])
printf("%d\n",ask(x,y));
else printf("%d\n",-1);
}
return 0;
}
反思
这一题写错了dfs中dp与递归的顺序,导致调了半天
[P3701]伪模版主席树
前很多天dky巨神推荐我写这道题。
发现这是一道特别“暴力”的最大流,于是把它水了。
思路
源点向byx所拥有的人连容量为生命的边
手气君所拥有的人向汇点连容量为生命的边
byx所拥有的人向所有的能打赢的人连容量为1的边
跑费用流就完事了
代码
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace orz{
const int N=1000,M=100000,inf=(1<<29);
const bool relation[5][5]{
{0,0,1,1,0},
{1,0,0,0,1},
{0,1,0,1,0},
{0,1,0,0,1},
{1,0,1,0,0}
};
struct node{
int role;
int num;
}a[110],b[110];
int n,m,nn,s,t,maxflow;
int head[N],ver[M],next[M],edge[M],deepth[N],tot=1;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline int get(){
char t=0;
int ans;
while(t>'Z'||t<'A')t=getchar();
switch(t){
case 'W':
ans=0;
break;
case 'J':
ans=1;
break;
case 'E':
ans=2;
break;
case 'Y':
ans=3;
break;
case 'H':
ans=4;
break;
}
while(t<='Z'&&t>='A')t=getchar();
return ans;
}
inline void add(int x,int y,int z){
next[++tot]=head[x];ver[tot]=y;edge[tot]=z;head[x]=tot;
next[++tot]=head[y];ver[tot]=x;edge[tot]=0;head[y]=tot;
}
inline bool BFS(){
int x,y;
queue<int>q;
for(int i=0;i<=nn;++i)deepth[i]=0;
q.push(s);deepth[s]=1;
while(q.size()){
x=q.front();q.pop();
for(int i=head[x];i;i=next[i]){
if(edge[i]&&!deepth[y=ver[i]]){
q.push(y);
deepth[y]=deepth[x]+1;
if(y==t)return true;
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==t)return flow;
int rest=flow,k,y;
for(int i=head[x];i;i=next[i]){
if(edge[i]&&deepth[y=ver[i]]==deepth[x]+1){
k=dinic(y,min(rest,edge[i]));
deepth[y]=k?deepth[y]:0;
edge[i]-=k;
edge[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
void ORZ(){
freopen("3701B.in","r",stdin);
int ay=0,by=0,aj=0,bj=0;
n=read();m=read();
s=2*n+1,t=s+1;
nn=t+1;
for(int i=1;i<=n;++i)
a[i].role=get(),ay+=a[i].role^3?0:1;
for(int i=1;i<=n;++i)
b[i].role=get(),by+=b[i].role^3?0:1;
for(int i=1;i<=n;++i)
a[i].num=read();
for(int i=1;i<=n;++i)
b[i].num=read();
for(int i=1;i<=n;++i)
add(s,i,a[i].role==1?a[i].num+ay:a[i].num);
for(int i=1;i<=n;++i)
add(i+n,t,b[i].role==1?b[i].num+by:b[i].num);;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
if(relation[a[i].role][b[j].role]){
add(i,j+n,1);
}
}
while(BFS())
maxflow+=dinic(s,inf);
printf("%d",min(m,maxflow));
}
}
int main(){
orz::ORZ();
return false;
}
[P2691]逃离
早上dky妹妹做这道题,我也写了下,非常水的最大流
思路
把每个点拆成入点和出点,中间连容量为1的边
源点向每个给出点的入点连容量为1的边
最外侧的点的出点向汇点连容量为1的边
最后最大流等于点数则可行
代码
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace orz{
const int N=10000,M=500000,inf=(1<<29);
const int dx[4]={0,0,1,-1},
dy[4]={1,-1,0,0};
int n,m,s,t,nn,maxflow;
int head[N],ver[M],edge[M],next[M],deepth[N],tot=1;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y,int z){
next[++tot]=head[x];ver[tot]=y;edge[tot]=z;head[x]=tot;
next[++tot]=head[y];ver[tot]=x;edge[tot]=0;head[y]=tot;
}
inline int toInt(int x,int y){
return (x-1)*n+y;
}
inline bool BFS(){
int x,y;
queue<int>q;
for(int i=0;i<=nn;++i)deepth[i]=0;
q.push(s);deepth[s]=1;
while(q.size()){
x=q.front();q.pop();
for(int i=head[x];i;i=next[i]){
if(edge[i]&&!deepth[y=ver[i]]){
q.push(y);
deepth[y]=deepth[x]+1;
if(y==t)return true;
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==t)return flow;
int rest=flow,k,y;
for(int i=head[x];i;i=next[i]){
if(edge[i]&&deepth[y=ver[i]]==deepth[x]+1){
k=dinic(y,min(rest,edge[i]));
deepth[y]=k?deepth[y]:0;
edge[i]-=k;
edge[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
void ORZ(){
// freopen("2691.in","r",stdin);
int x,y,k,d;
bool is_out;
n=read();m=read();
k=n*n;s=k*2+2;t=s+1;nn=t+1;
for(int i=0;i<m;++i){
x=read();y=read();
add(s,toInt(x,y),1);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
d=toInt(i,j);
is_out=false;
for(int z=0;z<4;++z){
x=i+dx[z];
y=j+dy[z];
if(x>n||y>n||x<1||y<1){
is_out=true;
continue;
}
add(d+k,toInt(x,y),1);
}
if(is_out)
add(d+k,t,1);
}
for(int i=1;i<=k;++i)
add(i,i+k,1);
while(BFS())
maxflow+=dinic(s,inf);
printf(maxflow>=m?"YES":"NO");
}
}
int main(){
orz::ORZ();
return false;
}
[P2483]【模板】k短路([SDOI2010]魔法猪学院)(A*)
蒟蒻不会这题正解,刚学了A*于是特判走一波
思路
hop数组(有声音的名字)表示从某个点到终点的最短路。在反图跑一边dijkstra便可求出
再跑A*得到答案
再加上一个无耻的特判可AC
代码
#include<cstdio>
#include<queue>
using namespace std;
namespace orz{
const int N=5050,M=200200;
struct E{
int x,y;
double z;
}Edge[M];
struct node{
double dis;
int nod;
node(int x,double y){
nod=x;dis=y;
}
};
struct star{
double dis,hop;
int nod;
star(int x,double y,double z){
nod=x;dis=y;hop=z;
}
};
bool operator<(const star&a,const star&b){
return a.hop>b.hop;
}
bool operator<(const node&a,const node&b){
return a.dis>b.dis;
}
int head[N],ver[M],next[M],tot=1;
double edge[M],hop[N];
bool vis[N];
inline void add(int x,int y,double z){
next[++tot]=head[x];head[x]=tot;ver[tot]=y;edge[tot]=z;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void A_star(int n){
priority_queue<node>q;
q.push(node(n,0));
hop[n]=0;
int x,y;
while(q.size()){
x=q.top().nod;q.pop();
if(vis[x])continue;
vis[x]=true;
for(int i=head[x];i;i=next[i]){
if(vis[y=ver[i]])continue;
if(hop[y]>hop[x]+edge[i]){
hop[y]=hop[x]+edge[i];
q.push(node(y,hop[y]));
}
}
}
}
inline int dijikstra(int n,double e){
double now=0,d;
int ans=0,x;
priority_queue<star>q;
q.push(star(1,0,0));
while(q.size()){
x=q.top().nod;d=q.top().dis;q.pop();
if(x==n){
now+=d;
if(now>e)return ans;
++ans;
}
for(int i=head[x];i;i=next[i])
q.push(star(ver[i],d+edge[i],d+edge[i]+hop[ver[i]]));
}
return ans;
}
void ORZ(){
// freopen("2483.in","r",stdin);
int n,m,x,y;
double e,z;
n=read();m=read();
scanf("%lf",&e);
if(e>1000000){
printf("2002000");
return ;
}
for(int i=0;i<m;++i){
Edge[i].x=read();Edge[i].y=read();scanf("%lf",&Edge[i].z);
}
for(int i=0;i<m;++i)
add(Edge[i].y,Edge[i].x,Edge[i].z);
for(int i=0;i<=n;++i)hop[i]=1E19;
A_star(n);
tot=1;
for(int i=0;i<=n;++i)head[i]=0;
for(int i=0;i<m;++i)
add(Edge[i].x,Edge[i].y,Edge[i].z);
int ans=dijikstra(n,e);
printf("%d",ans);
}
}
int main(){
orz::ORZ();
return false;
}
[NOIP2002]矩形覆盖
今天课上的一道搜索题,不需要剪枝爆搜就可以过
思路
枚举点属于哪个矩形,再判断矩形是否重叠就可以
如果要剪枝可以判断当前矩阵大小与目前最优解的大小进行最优性剪枝
这题数据较水,不剪枝199ms,剪枝22ms
代码
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=60,K=6;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
struct point{
int x,y;
}p[N],d[6];
struct square{
int x1,y1,x2,y2;
bool f;
}s[K];
int n,k,ans=(1<<28);
inline bool check(){
for(int i=0;i<k;++i){
if(!s[i].f)continue;
d[0].x=s[i].x1;
d[1].x=s[i].x1;
d[2].x=s[i].x2;
d[3].x=s[i].x2;
d[0].y=s[i].y1;
d[1].y=s[i].y2;
d[2].y=s[i].y1;
d[3].y=s[i].y2;
for(int j=0;j<k;++j){
if(!s[j].f||i==j)continue;
for(int z=0;z<4;++z){
if(d[z].x<=s[j].x2&&d[z].x>=s[j].x1&&d[z].y<=s[j].y2&&d[z].y>=s[j].y1)
return false;
}
}
}
return true;
}
inline int getSize(){
int sum=0;
for(int i=0;i<k;++i){
if(s[i].f)sum+=(s[i].x2-s[i].x1)*(s[i].y2-s[i].y1);
}
return sum;
}
void dfs(int t){
if(t==n){
ans=min(ans,getSize());
return ;
}
for(int i=0;i<k;++i){
if(s[i].f&&p[t].x<=s[i].x2&&p[t].y<=s[i].y2&&p[t].x>=s[i].x1&&p[t].y>=s[i].y1){
dfs(t+1);
return ;
}
}
for(int i=0;i<k;++i){
switch(s[i].f){
case true:
square tmp;
tmp=s[i];
s[i].x1=min(s[i].x1,p[t].x);
s[i].x2=max(s[i].x2,p[t].x);
s[i].y1=min(s[i].y1,p[t].y);
s[i].y2=max(s[i].y2,p[t].y);
if(check())dfs(t+1);
s[i]=tmp;
break;
case false:
s[i].f=true;
s[i].x1=s[i].x2=p[t].x;
s[i].y1=s[i].y2=p[t].y;
dfs(t+1);
s[i].f=false;
break;
}
}
}
void ORZ(){
n=read();k=read();
for(int i=0;i<n;++i)
p[i].x=read(),p[i].y=read();
dfs(0);
printf("%d",ans);
return;
}
}
int main(){
orz::ORZ();
return false;
}
[P2764]最小路径覆盖问题
很久以前讲的二分图题,拿dinic水了
思路
对于选择最少路径将所有点覆盖,可以转化为每个点度为1,求最大边数。于是可以将每个点拆成入点和出点,对于原图的每条边(x,y)从x的出点向y的入点连边,求二分图最大匹配即可。
这一题的重点在于输出方案,对于残量网络,显然对于正向边如果其残量为0则其被匹配,将所有残量为0的边新建一张图dfs输出结果。
代码
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace orz{
const int N=600,M=24020;
int head[N],ver[M],next[M],edge[M],deepth[N],tot=1;
int Head[N],Ver[M],Next[M],Tot=1;
int maxflow=0,n=0,nn,s,t,inDegree[N];
bool vis[N];
struct Edge{
int x,y;
}E[M];
inline void add(int x,int y,int z){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=0;
}
inline void Add(int x,int y){
Next[++Tot]=Head[x];Head[x]=Tot;Ver[Tot]=y;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline bool BFS(){
int x,y;
queue<int>q;
for(int i=0;i<=nn;++i)deepth[i]=0;
deepth[s]=1;
q.push(s);
while(q.size()){
x=q.front();q.pop();
for(int i=head[x];i;i=next[i]){
if(edge[i]&&!deepth[y=ver[i]]){
deepth[y]=deepth[x]+1;
if(y==t)return true;
q.push(y);
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==t)return flow;
int k,y,rest=flow;
for(int i=head[x];i;i=next[i]){
if(edge[i]&&deepth[y=ver[i]]==deepth[x]+1){
k=dinic(y,min(rest,edge[i]));
deepth[y]=k?deepth[y]:0;
edge[i]-=k;
edge[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
void dfs(int x){
printf("%d ",x);
for(int i=Head[x];i;i=Next[i]){
dfs(Ver[i]);
}
}
int QAQ(){
int m,x,y,num;
n=read();m=read();
s=2*n+1;t=s+1;nn=t+1;
for(int i=0;i<m;++i){
x=read();y=read();
add(x+n,y,1);
}
num=tot;
for(int i=1;i<=n;++i){
add(s,i+n,1);
add(i,t,1);
}
while(BFS())
maxflow+=dinic(s,(1<<29));
for(int i=2;i<=num;i+=2){
if(!edge[i]){
x=ver[i^1]-n;y=ver[i];
++inDegree[y];
Add(x,y);
}
}
for(int i=1;i<=n;++i){
if(!inDegree[i]){
dfs(i);
putchar('\n');
}
}
printf("%d",n-maxflow);
return false;
}
}
int main(){
return orz::QAQ();;
}
[P2774]方格取数问题
照着网络流24题做的,这题用到了最小割最大流定理
思路
对方格黑白染色,从源点向每一个白点连容量为数字大小的边,从每一个黑点向源点连容量大小为数字大小的边,对于每一个白点向其相邻黑点连容量无穷的边。跑最大流(最小割),答案是数字总和减去最大流。
证明
对于这个问题我们可以将其转化为删去权值最小的几个数使得矩阵中没有数字相邻。所以可以转化为求最小割(这证了个锤子)。
代码
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace orz{
const int N=10010,M=80020,maxn=110,inf=(1<<29);
const int dx[4]={0,0,1,-1},
dy[4]={1,-1,0,0};
int a[maxn][maxn];
int s,n,m,t,nn;
int head[N],deepth[N],ver[M],edge[M],next[M],tot=1;
bool color[maxn][maxn];
inline void add(int x,int y,int z){
next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=0;
}
bool BFS(){
int x,y;
queue<int>q;
for(int i=1;i<=nn;++i)
deepth[i]=0;
q.push(s);deepth[s]=1;
while(q.size()){
x=q.front();q.pop();
for(int i=head[x];i;i=next[i])
if(!deepth[y=ver[i]]&&edge[i]){
deepth[y]=deepth[x]+1;
if(y==t)return true;
q.push(y);
}
}
return false;
}
int dinic(int x,int flow){
if(x==t)return flow;
int y,k,rest=flow;
for(int i=head[x];i;i=next[i]){
if(deepth[y=ver[i]]==deepth[x]+1&&edge[i]){
k=dinic(y,min(rest,edge[i]));
deepth[y]=k?deepth[y]:0;
edge[i]-=k;
edge[i^1]+=k;
rest-=k;
}
}
return flow-rest;
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline int toNum(int x,int y){
return (x-1)*m+y;
}
int QAQ(){
int x,y,ans=0,sum=0;
n=read();m=read();
s=n*m+2,t=s+1,nn=t+1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
a[i][j]=read(),sum+=a[i][j];
for(int i=2;i<=m;++i)
color[1][i]=!color[1][i-1];
for(int i=2;i<=n;++i)
for(int j=1;j<=m;++j)
color[i][j]=!color[i-1][j];
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
switch(color[i][j]){
case true:
add(s,toNum(i,j),a[i][j]);
for(int z=0;z<4;++z){
x=i+dx[z],y=j+dy[z];
if(x>n||y>m||!x||!y)continue;
add(toNum(i,j),toNum(x,y),inf);
}
break;
case false:
add(toNum(i,j),t,a[i][j]);
break;
}
}
while(BFS())
ans+=dinic(s,inf);
printf("%d",sum-ans);
return false;
}
}
int main(){
return orz::QAQ();
}
[NOIP2015]运输计划
传说中的卡常神题,然而我并没被卡掉。
思路
二分加树上差分,不解释。
应对卡常的一个诡异技巧
对于这题来说经常遇到要遍历整棵的情况,由于一般来说树是按无向图存的,在判重和清空vis数组上难免要花一些时间。所以我们不妨先O(n)跑一遍dfs,将树转化为无向图即可节约判重的时间。
struct Edge{
int x,y,z;
}E[M];
void makeTreeBetter(int x){
int y;
for(int i=head[x];i;i=next[i]){
if(!vis[y=ver[i]]){
E[tot].x=x;
E[tot].y=y;
E[tot].z=edge[i];
vis[y]=true;
++tot;
makeTreeBetter(y);
}
}
}
挂了的一个地方
二分左端点应该从0开始,而不是从最短的路径。因为这个挂了三个点。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=400010,M=700000,inf=(1<<29);
int n,m;
int head[N],next[M],ver[M],edge[M],tot;
int deepth[N],f[N][25],path[N],theRoad[N],c[N],Orz[N],maxPath;
bool vis[N];
int Log2[N];
struct Edge{
int x,y,z;
}E[M];
struct project{
int x,y,lca,dis;
}p[N];
inline void logPre(){
for(int i=0;(1<<i)<=n;++i)
Log2[1<<i]=i;
int pos=0;
for(int i=0;i<=n;++i){
pos=max(pos,Log2[i]);
Log2[i]=pos;
}
}
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline void add(int x,int y,int z){
next[++tot]=head[x];head[x]=tot;ver[tot]=y;edge[tot]=z;
}
void makeTreeBetter(int x){
int y;
for(int i=head[x];i;i=next[i]){
if(!vis[y=ver[i]]){
E[tot].x=x;
E[tot].y=y;
E[tot].z=edge[i];
vis[y]=true;
++tot;
makeTreeBetter(y);
}
}
}
void dfs(int x,int dep){
deepth[x]=dep;
int k=Log2[dep-1],y;
for(int i=1;i<=k;++i)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=next[i]){
f[y=ver[i]][0]=x;
path[y]=path[x]+edge[i];
theRoad[y]=edge[i];
dfs(y,dep+1);
}
}
inline int LCA(int x,int y){
if(deepth[y]>deepth[x])swap(x,y);
int k=Log2[deepth[x]-deepth[y]];
for(int i=k;i>=0;--i){
if(deepth[f[x][i]]<deepth[y])continue;
x=f[x][i];
}
if(x==y)return x;
k=Log2[deepth[x]];
for(int i=k;i>=0;--i){
if(f[x][i]==f[y][i])continue;
x=f[x][i];y=f[y][i];
}
return f[x][0];
}
int dky(int x){
Orz[x]=c[x];
for(int i=head[x];i;i=next[i])
Orz[x]+=dky(ver[i]);
return Orz[x];
}
inline bool check(int d){
int k=0;
for(int i=1;i<=n;++i)c[i]=0;
for(int i=0;i<m;++i){
if(p[i].dis>d){
c[p[i].lca]-=2;
++c[p[i].x];++c[p[i].y];
++k;
}
}
dky(1);
for(int i=1;i<=n;++i){
if(Orz[i]==k){
if(theRoad[i]>=maxPath-d)
return true;
}
}
return false;
}
void ORZ(){
int x,y,z,l=0,r=0,mid;
n=read();m=read();
logPre();
for(int i=1;i<n;++i){
x=read(),y=read(),z=read();
add(x,y,z);add(y,x,z);
}
tot=0;
vis[1]=true;
makeTreeBetter(1);
for(int i=1;i<=n;++i)head[i]=0;
tot=0;
for(int i=0;i<n-1;++i)
add(E[i].x,E[i].y,E[i].z);
path[1]=0;
dfs(1,1);
for(int i=0;i<m;++i){
p[i].x=read();p[i].y=read();
p[i].lca=LCA(p[i].x,p[i].y);
p[i].dis=path[p[i].x]+path[p[i].y]-2*path[p[i].lca];
l=min(p[i].dis,l);
r=max(p[i].dis,r);
}
maxPath=r;
while(l<r){
mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
printf("%d",l);
return ;
}
}
int main(){
orz::ORZ();
return false;
}
[CF896C]Willem, Chtholly and Seniorious
珂朵莉树模版题
死宅学死宅数据结构
鲁迅
珂朵莉树简介
基于std::set的一种暴力数据结构,在数据随机且操作有区间赋值的数据结构题中使用(然而这样的题几乎没有)。或者不会正解拿来打暴力。
主要思想
结构体存储l到r区间全部为v。进行一些暴力维护
代码
#include<cstdio>
#include<set>
#include<algorithm>
#include<vector>
#define ll long long
#define IT set<node>::iterator
using namespace std;
namespace orz{
const int N=100100;
struct node{
int l,r;
mutable ll value;
node(int L,int R,ll V){
l=L;r=R;value=V;
}
node(const int &pos){
r=-1;value=0;l=pos;
}
bool operator < (const node &a)const {
return l<a.l;
}
};
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline ll pow(ll a,ll b,ll mod){
ll res=1;
a=a%mod;
while(b){
if(b&1)
res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll n,m,vmax;
unsigned long long seed;
ll a[N];
set<node>s;
inline IT split(int pos){
IT it=s.lower_bound(node(pos));
if(it!=s.end()&&it->l==pos)
return it;
--it;
int l=it->l,r=it->r;
ll v=it->value;
s.erase(it);
s.insert(node(l,pos-1,v));
return s.insert(node(pos,r,v)).first;
}
inline void assign_val(int l,int r,ll val){
IT itr=split(r+1),itl=split(l);
s.erase(itl,itr);
s.insert(node(l,r,val));
}
inline void add(int l,int r,ll val){
IT itr=split(r+1),itl=split(l);
for(;itl!=itr;++itl)
itl->value+=val;
}
inline ll rank(int l,int r,int k){
vector<pair<ll,int> >vp;
IT itr=split(r+1),itl=split(l);
for(;itl!=itr;++itl)
vp.push_back(pair<ll,int>(itl->value,itl->r-itl->l+1));
sort(vp.begin(),vp.end());
for(vector<pair<ll,int> >::iterator it=vp.begin();it!=vp.end();++it){
k-=it->second;
if(k<=0)
return it->first;
}
}
ll sum(int l,int r,int ex,int mod){
IT itr=split(r+1),itl=split(l);
ll ans=0;
for(;itl!=itr;++itl)
ans=(ans+(ll)(itl->r-itl->l+1)*pow(itl->value,(ll)ex,mod))%mod;
return ans;
}
inline unsigned long long rand(){
unsigned long long ret=seed;
seed=(seed*7+13)%1000000007;
return ret;
}
int QAQ(){
int l,r,op;
long long x,y;
n=read();m=read();seed=read();vmax=read();
for(int i=1;i<=n;++i)
s.insert(node(i,i,rand()%vmax+1));
s.insert(node(n+1,n+1,0));
for(int i=1;i<=m;++i){
op=rand()%4+1;
l=rand()%n+1;
r=rand()%n+1;
if(l>r)swap(l,r);
if(op==3)x=rand()%(r-l+1)+1;
else x=rand()%vmax+1;
if(op==4)y=rand()%vmax+1;
switch(op){
case 1:
add(l,r,(ll)x);
break;
case 2:
assign_val(l,r,(ll)x);
break;
case 3:
printf("%lld\n",rank(l,r,x));
break;
case 4:
printf("%lld\n",sum(l,r,x,y));
break;
}
}
return false;
}
}
int main(){
return orz::QAQ();
}
[AHOI2009]中国象棋
寒假集训时讲的dp题
思路
题意可转化为每行每列不可以放超过两个棋子的方案数
设\(f[i][j][k]\)为前i行有j列放一个棋子,k列放两个棋子(n-i-k)即为不放棋子的列的数量,可以省略这一维。
只后考虑在第i行棋子的放置情况。对于每一种情况考虑其可由那些之前的状态推来。加起来即为所求答案。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=200;
const long long MOD=9999973;
inline int read(){
int a=1,b=0;char t;
do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a*b;
}
inline long long C2(int x){
return x*(x-1)/2;
}
long long a[N][N][N];
int QAQ(){
int n,m;
long long ans=0;
n=read();m=read();
a[0][0][0]=1;
for(int i=1;i<=m;++i)
for(int j=0;j<=n;++j)
for(int k=0;k<=n;++k){
a[i][j][k]=(a[i][j][k]+a[i-1][j][k] )%MOD;//不放棋子
if(j>=1)a[i][j][k]=(a[i][j][k]+(n-k-j+1) *a[i-1][j-1][k] )%MOD;//在空白位置放一个
if(k>=1)a[i][j][k]=(a[i][j][k]+(j+1) *a[i-1][j+1][k-1])%MOD;//在有一的位置放一个
if(j>=2)a[i][j][k]=(a[i][j][k]+C2(n-k-j+2) *a[i-1][j-2][k] )%MOD;//在空白的位置放两个
if(k>=2)a[i][j][k]=(a[i][j][k]+C2(j+2) *a[i-1][j+2][k-2])%MOD;//在有一的位置放两个
if(k>=1)a[i][j][k]=(a[i][j][k]+(j*(n-j-k+1))*a[i-1][j] [k-1])%MOD;//一个放在空白位置一个放在有一的位置
}
for(int i=0;i<=n;++i)
for(int j=0;j<=n;++j)
ans=(ans+a[m][i][j])%MOD;
printf("%lld",ans%MOD);
return false;
}
}
int main(){
return orz::QAQ();
}
[P2783]有机化学之神偶尔会做作弊
郭老师亲力推荐的黑题,郭老师秒切了而我却交了一页。
题目链接
思路
太监完了之后求两点之间路径即可
对tarjan的新认知
郭老师教了我们一种不需要记录割边的求边双的方式。
void tarjan(int x,int father){
dfn[x]=low[x]=++num;
stack[++tp]=x;
int y;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(y==father)continue;
if(!dfn[y]){
tarjan(y,x);
low[x]=min(low[x],low[y]);
}
else low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
++cnt;
do{
y=stack[tp--];
c[y]=cnt;
}while(y!=x);
}
}
在外面使用栈来判断,其思路和强连通略像。
蒟蒻的代码
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=10010,M=100050;
int next[M],head[N],ver[M],tot=1,cnt=0;
int low[N],dfn[N],num;
int c[N];
bool bridge[M],vis[N];
int son[N],depth[N],fa[N],top[N],size[N];
int two[30];
int stack[N],tp=0;
int n,m,q;
struct E{
int x,y;
}edge[M];
inline int read(){
int b=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return b;
}
inline void add(int x,int y){
next[++tot]=head[x],ver[tot]=y,head[x]=tot;
next[++tot]=head[y],ver[tot]=x,head[y]=tot;
}
void tarjan(int x,int father){
dfn[x]=low[x]=++num;
stack[++tp]=x;
int y;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(y==father)continue;
if(!dfn[y]){
tarjan(y,x);
low[x]=min(low[x],low[y]);
}
else low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
++cnt;
do{
y=stack[tp--];
c[y]=cnt;
}while(y!=x);
}
}
void dky(int x,int father,int deep){
depth[x]=deep;size[x]=1;fa[x]=father;
int y,weight=-100000;
for(int i=head[x];i;i=next[i]){
if((y=ver[i])==father)continue;
dky(y,x,deep+1);
size[x]+=size[y];
if(size[y]>weight){
weight=size[y];
son[x]=y;
}
}
}
void dkw(int x,int topf){
top[x]=topf;
int y;
if(!son[x])return ;
dkw(son[x],topf);
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(y==fa[x]||y==son[x])continue;
dkw(y,y);
}
}
int LCA(int x,int y){
while(top[x]^top[y]){
if(depth[top[x]]<depth[top[y]])swap(x,y);
x=fa[top[x]];
}
if(depth[x]>depth[y])swap(x,y);
return x;
}
void print(int x){
if(x){
print(x>>1);
putchar(x&1?'1':'0');
}
}
int QAQ(){
int x,y;
n=read(),m=read();
for(int i=0;i<m;++i)
add(edge[i].x=read(),edge[i].y=read());
tarjan(1,0);
for(int i=1;i<=n;++i)
head[i]=0;
tot=1;
for(int i=0;i<m;++i){
if(c[edge[i].x]==c[edge[i].y])continue;
add(c[edge[i].x],c[edge[i].y]);
}
dky(1,0,1);
dkw(1,1);
q=read();
while(q--){
x=read();y=read();
print(depth[c[x]]+depth[c[y]]-2*depth[LCA(c[x],c[y])]+1);
putchar('\n');
}
return false;
}
}
int main(){
return orz::QAQ();
}
[P2812]校园网络
原题链接
这道题在洛谷上是双倍经验,同一份代码,双倍的快乐。
思路
求强联通分量后缩点,第一问即为入度为零的点数。第二问为入度为零与出度为零点数取min,即将出度为零的点向入度为零的连边。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=10100,M=5005000;
int head[N],ver[M],next[M],tot=1,tol=0;
int dfn[N],low[N],stack[N],top=0,num=0,cnt=0;
int c[N];
bool vis[N];
int inDgreeCnt=0,outDgreeCnt=0;
int inDgree[N],outDgree[N];
struct E{
int x,y;
}Edge[M];
inline void add(int x,int y){
next[++tot]=head[x],head[x]=tot,ver[tot]=y;
}
inline int read(){
int a=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{a=a*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a;
}
void tarjan(int x){
dfn[x]=low[x]=++num;
stack[++top]=x;
vis[x]=true;
int y;
for(int i=head[x];i;i=next[i]){
if(!dfn[y=ver[i]])
tarjan(y),low[x]=min(low[x],low[y]);
else if(vis[y])
low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x]){
++cnt;
do{
y=stack[top--];
vis[y]=false;
c[y]=cnt;
}while(y!=x);
}
}
int QAQ(){
int n,x,y;
n=read();
for(int i=1;i<=n;++i)
while(y=read())
Edge[tol].x=i,Edge[tol++].y=y;
for(int i=0;i<tol;++i)
add(Edge[i].x,Edge[i].y);
for(int i=1;i<=n;++i)
if(!dfn[i])
tarjan(i);
if(cnt==1){
printf("%d\n%d",1,0);
return false;
}
for(int i=0;i<tol;++i)
if(c[x=Edge[i].x]^c[y=Edge[i].y])
++outDgree[c[x]],++inDgree[c[y]];
for(int i=1;i<=cnt;++i)
if(!inDgree[i])
++inDgreeCnt;
for(int i=1;i<=cnt;++i)
if(!outDgree[i])
++outDgreeCnt;
printf("%d\n%d",inDgreeCnt,max(inDgreeCnt,outDgreeCnt));
return false;
}
}
int main(){
return orz::QAQ();
}
[P2860]冗余路径Redundant Paths
边双联通分量裸题
思路
边双缩点后答案为度为一的节点数除以二(向上取整)
意义为每连一条边可以形成一个环消灭两个点。
代码
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
namespace orz{
const int N=5050,M=21100;
int root;
int n,m;
int head[N],next[M],ver[M],tot=1;
int dfn[N],low[N],num=0,cnt=0;
int stack[N],top=0;
bool cut[N];
vector<int>c[N];
inline void add(int x,int y){
next[++tot]=head[x],head[x]=tot,ver[tot]=y;
next[++tot]=head[y],head[y]=tot,ver[tot]=x;
}
inline int read(){
int a=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{a=a*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a;
}
inline void clear(const int &x){
for(int i=1;i<=n;++i)c[i].clear();
for(int i=1;i<=x;++i)
head[i]=dfn[i]=low[i]=cut[i]=0;
tot=1;cnt=0;num=0;
}
void tarjan(int x){
dfn[x]=low[x]=++num;
stack[++top]=x;
if(x==root&&!head[x]){
c[++cnt].push_back(x);
return;
}
int flag=0,y=0;
for(int i=head[x];i;i=next[i]){
y=ver[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){
++flag;
if(x!=root||flag>1)cut[x]=true;
++cnt;
int z;
do{
z=stack[top--];
c[cnt].push_back(z);
}while(z!=y);
c[cnt].push_back(x);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int QAQ(){
int x,y;
n=read();m=read();
for(int i=0;i<m;++i)add(read(),read());
for(int i=1;i<=n;++i)
if(!dfn[i])
root=i,tarjan(i);
int cutCnt=0;
int ans=0;
for(int i=1;i<=cnt;++i){
cutCnt=0;
for(int j=0;j<c[i].size();++j)
if(cut[c[i][j]])++cutCnt;
ans+=cutCnt^1?0:1;
}
printf("%d",ans&1?(ans>>1)+1:ans>>1);
return false;
}
}
int main(){
return orz::QAQ();
}
[P2045]方格取数加强版
原题链接
费用流模板题
思路
如果k<=2的话dp还比较方便,但随着k的增长随之而来的是转移方程的复杂与状态维数的过大。
将每个点拆成入点和出点,从入点向出点连一条费用为点权的边,连k-1条费用为零的边,表示每个数只能被取一次。出点与入点之间连流量无限,费用为零的边。跑最大费用最大流即可。
代码
#include<cstdio>
#include<queue>
#define inPoint true
#define outPoint false
using namespace std;
namespace orz{
const int maxn=55,N=10100,M=80000,inf=(1<<29);
int head[N],ver[M],next[M],edge[M],tot=1;
int cost[M],dis[N],incf[N],pre[N];
int nn,s,t,n,k;
int a[maxn][maxn];
int ans=0;
bool vis[N];
inline int read(){
int a=0;char t;
do{t=getchar();}while(t>'9'||t<'0');
do{a=a*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
return a;
}
inline void add(int x,int y,int z,int c){
next[++tot]=head[x];head[x]=tot;ver[tot]=y;edge[tot]=z;cost[tot]=c;
next[++tot]=head[y];head[y]=tot;ver[tot]=x;edge[tot]=0;cost[tot]=-c;
}
inline int toNum(const int &x,const int &y,const int &z){
return z?(x-1)*n+y:n*n+(x-1)*n+y;
}
bool SPFA(){
int x,y;
queue<int>q;
for(int i=0;i<nn;++i)dis[i]=-inf;
q.push(s);
dis[s]=0;
incf[s]=inf;
while(q.size()){
x=q.front();
q.pop();
vis[x]=false;
for(int i=head[x];i;i=next[i]){
if(!edge[i])continue;
y=ver[i];
if(dis[x]+cost[i]>dis[y]){
dis[y]=dis[x]+cost[i];
incf[y]=min(incf[x],edge[i]);
pre[y]=i;
if(!vis[y])vis[y]=true,q.push(y);
}
}
}
return dis[t]^(-inf)?true:false;
}
void update(){
int x=t;
while(x!=s){
int i=pre[x];
edge[i]-=incf[t];
edge[i^1]+=incf[t];
x=ver[i^1];
}
ans+=dis[t]*incf[t];
}
int QAQ(){
n=read();k=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
a[i][j]=read();
s=n*n*2+1;
t=s+1;
nn=t+1;
add(s,toNum(1,1,inPoint),inf,0);
add(toNum(n,n,outPoint),t,inf,0);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
add(toNum(i,j,inPoint),toNum(i,j,outPoint),1,a[i][j]);
add(toNum(i,j,inPoint),toNum(i,j,outPoint),k-1,0);
if(i^n)
add(toNum(i,j,outPoint),toNum(i+1,j,inPoint),inf,0);
if(j^n)
add(toNum(i,j,outPoint),toNum(i,j+1,inPoint),inf,0);
}
while(SPFA())
update();
printf("%d",ans);
return false;
}
}
int main(){
return orz::QAQ();
}