国家集训队2011]happiness(吴确)

Posted Forever_goodboy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了国家集训队2011]happiness(吴确)相关的知识,希望对你有一定的参考价值。

                              1873. [国家集训队2011]happiness(吴确)

★★★   输入文件:nt2011_happiness.in   输出文件:nt2011_happiness.out   简单对比
时间限制:1 s   内存限制:512 MB

【试题来源】

2011中国国家集训队命题答辩

【问题描述】

高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。

【输入格式】

第一行两个正整数n,m。
接下来是六个矩阵
第一个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择文科获得的喜悦值。
第二个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学选择理科获得的喜悦值。
第三个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择文科获得的额外喜悦值。
第四个矩阵为n-1行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i+1行第j列的同学同时选择理科获得的额外喜悦值。
第五个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择文科获得的额外喜悦值。
第六个矩阵为n行m-1列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学与第i行第j+1列的同学同时选择理科获得的额外喜悦值。

【输出格式】

输出一个整数,表示喜悦值总和的最大值

【样例输入】

1 2
1 1
100 110
1
1000

【样例输出】

1210

【样例说明】

两人都选理,则获得100+110+1000的喜悦值。

【数据规模和约定】

对于10%以内的数据,n,m<=4
对于30%以内的数据,n,m<=8
对于100%以内的数据,n,m<=100 数据保证答案在2^30以内
对于100%的数据,时间限制为0.5s。
首先不用想,这肯定是个最小割……不妨将其解题过程记录下来,以作为一个典型的最小割模型题。
 
“最小割”求的是一个“最小”的值,而本题需要最大化喜悦值之和,所以很显然,在最小割中被割掉的边应该意味着“放弃该喜悦值”。也就是说,最小割是放弃掉的所有喜悦值。
 
首先,为了简单起见,我们不妨选取一对相邻点进行研究。不妨设是这样的:
技术分享
 
其中S是源点,x,y是我们研究的两个点(也就是原问题中的两个学生),T是汇点。为了方便起见,没有画出边。
 
文中可能会用到一些符号:w(a,b)表示边(a,b)的容量,x选文的喜悦值为W_x文,x,y都选文的额外喜悦值是W_同文,以此类推。

最小割模型常用的一个手段是:每个点都需要在“在S集”和“在T集”两个事件中选一个。我们规定,“在S集”代表学文,“在T集”代表学理。
 
那么总的快乐值是:W_x文 + W_x理 + W_y文 + W_y理 + W_同文 + W_同理,割值就是从其中扣掉的快乐值。
 
下面考虑四种情况:
①x在S,y在S
技术分享
红线代表割,左边是S集,右边是T集。
割边容量之和:w(x,T) + w(y,T)
“放弃的快乐值”之和:W_x理 + W_y理 + W_同理 (两个人同时学文)
即:w(x,T) + w(y,T) = W_x理 + W_y理 + W_同理
 
②x在S,y在T
技术分享
x学文,y学理。
w(x,T) + w(S,y) + w(x,y) = W_x理 + W_y文 + W_同文 + W_同理
 
类似可以推出后两种情况:
x在T,y在S:w(S,x) + w(y,T) + w(y,x) = W_x文 + W_y理 + W_同文 + W_同理
x在T,y在T:w(S,x) + w(S,y) = W_x文 + W_y文 + W_同文
 
把四种情况写在一起:
 
①SS:w(x,T) + w(y,T) = W_x理 + W_y理 + W_同理
 
②ST:w(x,T) + w(S,y) + w(x,y) = W_x理 + W_y文 + W_同文 + W_同理
 
③TS:w(S,x) + w(y,T) + w(y,x) = W_x文 + W_y理 + W_同文 + W_同理
 
④TT:w(S,x) + w(S,y) = W_x文 + W_y文 + W_同文
 
然后,怎么设置这些边的容量呢?
 
我们看①,左边有两项,右边有三项。
秉承“对称”的思想,我们令:
w(x,T) = W_x理 + 0.5W_同理,
w(y,T) = W_y理 + 0.5W_同理。
类似地,
w(S,x) = W_x文 + 0.5W_同文,
w(S,y) = W_y文 + 0.5W_同文。
 
然后我们检查②和③。以②为例,将w(x,T)和w(S,y)代入并化简:
 
w(x,y) = 0.5 ( W_同文 + W_同理 )
 
同样地由③得:
 
w(y,x) = 0.5 ( W_同文 + W_同理 )
 
把x,y扩展至整张棋盘:
对于某个人(i,j),设它对应的顶点是x。
连边(S,x),容量为:W_x理 + 0.5 ( x和所有邻居的W_同理之和 ) 
连边(x,T),容量为:W_x文 + 0.5 ( x和所有邻居的W_同文之和 )
对于某两个相邻的人x和y(y可能在x的上下左右),
连边(x,y),容量为:0.5 ( W_同文 + W_同理 )
 
用所有的快乐值之和(其实就是输入的那六个矩阵的所有元素之和)减去最小割(即最大流)的值即为答案。这里有一点细节:实数容量的网络流不好求,可以把所有边的容量*2,最后输出答案时/2即可。
打代码的时候不小心把n*m打成了n+m,wa了四次,手残.......
  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<algorithm>
  5 using namespace std;
  6 #define inf 100000007
  7 #define int long long
  8 int read() {
  9     int s=0,f=1;
 10     char ch=getchar();
 11     while(ch>9||ch<0) {
 12         if(ch==-) {
 13             f=-1;
 14         }
 15         ch=getchar();
 16     }
 17     while(ch>=0&&ch<=9) {
 18         s=(s<<1)+(s<<3)+(ch^48);
 19         ch=getchar();
 20     }
 21     return s*f;
 22 }
 23 int n,m,sum;
 24 int wen_sin[101][101],li_sin[101][101],wen_xia[101][101],li_xia[101][101],wen_you[101][101],li_you[101][101];
 25 int dis[102][102],num[101][101],zong,tot,S,T,r[10005];
 26 int sum_li[10002],sum_wen[10002];
 27 struct oo {
 28     int to,vv,next;
 29 } c[1000005];
 30 void add(int x,int y,int z) {
 31     c[tot].to=y;
 32     c[tot].vv=z;
 33     c[tot].next=r[x];
 34     r[x]=tot++;
 35 }
 36 void init() {
 37     memset(r,-1,sizeof(r));
 38     n=read();
 39     m=read();
 40     T=n*m+1;
 41     for(int i=1; i<=n; i++) {
 42         for(int j=1; j<=m; j++) {
 43             num[i][j]=++zong;
 44         }
 45     }
 46     for(int i=1; i<=n; i++) {
 47         for(int j=1; j<=m; j++) {
 48             wen_sin[i][j]=read()*2;
 49             sum+=wen_sin[i][j];
 50         }
 51     }
 52     for(int i=1; i<=n; i++) {
 53         for(int j=1; j<=m; j++) {
 54             li_sin[i][j]=read()*2;
 55             sum+=li_sin[i][j];
 56         }
 57     }
 58     for(int i=1; i<n; i++) {
 59         for(int j=1; j<=n; j++) {
 60             wen_xia[i][j]=read()*2;
 61             sum_wen[num[i][j]]+=wen_xia[i][j];
 62             sum_wen[num[i+1][j]]+=wen_xia[i][j];
 63             sum+=wen_xia[i][j];
 64         }
 65     }
 66     for(int i=1; i<n; i++) {
 67         for(int j=1; j<=m; j++) {
 68             li_xia[i][j]=read()*2;
 69             sum_li[num[i][j]]+=li_xia[i][j];
 70             sum_li[num[i+1][j]]+=li_xia[i][j];
 71             sum+=li_xia[i][j];
 72         }
 73     }
 74     for(int i=1; i<=n; i++) {
 75         for(int j=1; j<m; j++) {
 76             wen_you[i][j]=read()*2;
 77             sum_wen[num[i][j]]+=wen_you[i][j];
 78             sum_wen[num[i][j+1]]+=wen_you[i][j];
 79             sum+=wen_you[i][j];
 80         }
 81     }
 82     for(int i=1; i<=n; i++) {
 83         for(int j=1; j<m; j++) {
 84             li_you[i][j]=read()*2;
 85             sum_li[num[i][j]]+=li_you[i][j];
 86             sum_li[num[i][j+1]]+=li_you[i][j];
 87             sum+=li_you[i][j];
 88         }
 89     }
 90 }
 91 // S li T wen
 92 void build() {
 93     for(int i=1; i<=n; i++) {
 94         for(int j=1; j<=m; j++) {
 95             int x=li_sin[i][j]+sum_li[num[i][j]]/2;
 96             add(S,num[i][j],x);
 97             add(num[i][j],S,0);
 98             int y=wen_sin[i][j]+sum_wen[num[i][j]]/2;
 99             add(num[i][j],T,y);
100             add(T,num[i][j],0);
101             if(i<n) {
102                 add(num[i][j],num[i+1][j],(li_xia[i][j]+wen_xia[i][j])/2);
103                 add(num[i+1][j],num[i][j],0);
104                 add(num[i+1][j],num[i][j],(li_xia[i][j]+wen_xia[i][j])/2);
105                 add(num[i][j],num[i+1][j],0);
106             }
107             if(j<m) {
108                 add(num[i][j],num[i][j+1],(li_you[i][j]+wen_you[i][j])/2);
109                 add(num[i][j+1],num[i][j],0);
110                 add(num[i][j+1],num[i][j],(li_you[i][j]+wen_you[i][j])/2);
111                 add(num[i][j],num[i][j+1],0);
112             }
113         }
114     }
115 }
116 int queue[1000005],head,tail,deep[10005];
117 bool bfs(int s,int t) {
118     memset(deep,0,sizeof(deep));
119     head=tail=0;
120     deep[s]=1;
121     queue[++tail]=s;
122     while(head<tail) {
123         int opt=queue[++head];
124         for(int i=r[opt]; ~i; i=c[i].next) {
125             if(c[i].vv&&!deep[c[i].to]) {
126                 deep[c[i].to]=deep[opt]+1;
127                 queue[++tail]=c[i].to;
128                 if(c[i].to==t) {
129                     return 1;
130                 }
131             }
132         }
133     }
134     return 0;
135 }
136 int dfs(int opt,int fw) {
137     if(opt==T) {
138         return fw;
139     }
140     int tmp=fw,k;
141     for(int i=r[opt]; ~i; i=c[i].next) {
142         if(tmp&&c[i].vv&&deep[c[i].to]==deep[opt]+1) {
143             k=dfs(c[i].to,min(c[i].vv,tmp));
144             if(!k) {
145                 deep[c[i].to]=0;
146                 continue;
147             }
148             c[i].vv-=k;
149             c[i^1].vv+=k;
150             tmp-=k;
151         }
152     }
153     return fw-tmp;
154 }
155 int dinic(int s,int t) {
156     int ans=0;
157     while(bfs(s,t)) {
158         ans+=dfs(s,inf);
159     }
160     return ans;
161 }
162 int Main(){
163     freopen("nt2011_happiness.in","r",stdin);
164     freopen("nt2011_happiness.out","w",stdout);
165     init();
166     build();
167     int ans=dinic(S,T);
168     printf("%d\n",(sum-ans)>>1);
169     return 0;
170 }
171 int hehe=Main();
172 signed main() {
173     ;
174 }

 




















以上是关于国家集训队2011]happiness(吴确)的主要内容,如果未能解决你的问题,请参考以下文章

[国家集训队2011]happiness

国家集训队2011 happiness

有没有办法将国家/地区的两个字母字母代码显示在地图上?

数据结构 happiness

bzoj 2127: happiness

Happiness - Change