国家集训队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。

 参考了大佬的题解http://blog.sina.com.cn/s/blog_c5566b0f0102v7yo.html

首先不用想,这肯定是个最小割……不妨将其解题过程记录下来,以作为一个典型的最小割模型题。

“最小割”求的是一个“最小”的值,而本题需要最大化喜悦值之和,所以很显然,在最小割中被割掉的边应该意味着“放弃该喜悦值”。也就是说,最小割是放弃掉的所有喜悦值。

首先,为了简单起见,我们不妨选取一对相邻点进行研究。不妨设是这样的:

其中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 }
时间: 2024-10-23 21:44:38

国家集训队2011]happiness(吴确)的相关文章

[国家集训队2011]happiness(吴确) (最小割)

2017-08-09 19:03:49 [试题来源] 2011中国国家集训队命题答辩 [问题描述] 高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友.这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值.作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大. [输入格式] 第一行两个正整数n,m.接下来是六个矩阵第一个矩阵为n行m列 此矩阵的第i行

国家集训队2011 happiness

[试题来源] 2011中国国家集训队命题答辩 [问题描述] 高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友.这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值.作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大. [输入格式] 第一行两个正整数n,m.接下来是六个矩阵第一个矩阵为n行m列 此矩阵的第i行第j列的数字表示座位在第i行第j列的同学

cogs 1901. [国家集训队2011]数颜色

Cogs 1901. [国家集训队2011]数颜色 ★★★   输入文件:nt2011_color.in   输出文件:nt2011_color.out   简单对比时间限制:0.6 s   内存限制:512 MB [试题来源] 2011中国国家集训队命题答辩 [问题描述] 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会像你发布如下指令:1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔.2. R P Col 把第P支画笔替换为

[国家集训队2011]旅游(宋方睿)

1867. [国家集训队2011]旅游(宋方睿) ★★★★   输入文件:nt2011_travel.in   输出文件:nt2011_travel.out   简单对比时间限制:1 s   内存限制:512 MB [试题来源] 2011中国国家集训队命题答辩 [问题描述] Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径.换句话说,T 城中只有N - 1

[补档][国家集训队2011]单选错位

题目 gx和lc去参加noip初赛,其中有一种题型叫单项选择题,顾名思义,只有一个选项是正确答案. 试卷上共有n道单选题,第i道单选题有ai个选项,这ai个选项编号是1,2,3,-,ai,每个选项成为正确答案的概率都是相等的.lc采取的策略是每道题目随机写上1-ai的某个数作为答案选项,他用不了多少时间就能期望做对sigma(1/ai)道题目.gx则是认认真真地做完了这n道题目,可是等他做完的时候时间也所剩无几了,于是他匆忙地把答案抄到答题纸上,没想到抄错位了:第i道题目的答案抄到了答题纸上的第

[国家集训队2011]种树 (神贪心~~)

/* 莫名其妙就做了集训队的题 不过..数据好水 codevs 1342 哈哈哈乱搞85 贪心的(好像有bug2333)照起点和终点 然后dp搞答案 这个应该很简单的 要滚一下数组 同桌打的暴力dp 55好像 思路一样的 就是省去了那个正确性不一定的贪心 */ #include<cstdio> #include<cstring> #define maxn 200010 using namespace std; int n,m,f[2][maxn][2],a[maxn],c[maxn

[BZOJ 2141][国家集训队 2011]排队 树状数组套平衡树

这道题也就是一个动态逆序对嘛,本质上就是个查询区间排名 刚刚打了一道 [CQOI 2011]动态逆序对  用的线段树套平衡树,代码如下: #include<iostream> #include<cstdio> #include<cstring> using namespace std; #define pos(i,a,b) for(int i=(a);i<=(b);i++) #define N 101000 #include<cstdlib> #def

【bzoj2141】排队 [国家集训队2011]排队(魏铭) 树套树 线段树套替罪羊树

这个题就是动态偏序对,每次操作做两个删除两个插入就好了. #include<cstdio> #include<iostream> #include<cstring> #define MAXN 100010 using namespace std; typedef long long LL; typedef double D; const D a=0.756; LL ans; struct ScapeGoat_Tree { ScapeGoat_Tree *ch[2]; i

BZOJ2160:[国家集训队2011]拉拉队排练

2160: 拉拉队排练 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 1427  Solved: 576[Submit][Status][Discuss] Description 艾 利斯顿商学院篮球队要参加一年一度的市篮球比赛了.拉拉队是篮球比赛的一个看点,好的拉拉队往往能帮助球队增加士气,赢得最终的比赛.所以作为拉拉队队长 的楚雨荨同学知道,帮助篮球队训练好拉拉队有多么的重要.拉拉队的选拔工作已经结束,在雨荨和校长的挑选下,n位集优秀的身材.