@loj - 2288@「THUWC 2017」大葱的神力
@description@
[提交答案题] N 个物品 M 个背包的背包问题。
@solution@
当前得分 70 + 4 + 2 + 1 = 77。
@data - 1@
观察数据点 N = M = 5。
自己手玩一下即可,你看我可以人脑破解 NPC 问题。
1 号,2 号,3 号,5 号物品都可以达到自己的最优选择。而枚举 4 号的物品的摆放会发现只有 4 号物品放入第 1 个背包时才能达到最优解。
最后玩出来的结果是:
3
3
2
1
1
当然也可以写个模拟退火验证一下。
@data - 2@
观察数据 N = 25, M = 8。
理论上可以用搜索 + 剪枝,但是因为我实在太懒了,所以直接写了一个模拟退火。
跑了大概 3 分钟跑出来了最优解 108。
@data - 3@
观察数据 M = 1。
很经典的 01 背包,直接写 dp 就可以过了。
@data - 4@
观察数据,所有物品的体积都是 233。
这个时候就可以确定每个背包能装的物品数量是多少,背包容量和物体体积转换为了物品数量限制。
这个时候就是一个最大权匹配,写个费用流就可以了。
@data - 5@
观察数据,所有物品的体积都是 2233。
跟 data - 4 一样的解法,只是时间跑得久一些,大概 5 秒。
@data - 6@
观察数据,所有物品的体积都是 19260817 在 19660600 ~ 19660720 这个范围以内。
体积不一样,好像并不能再使用费用流了?
但实际上物品之间的体积都是微小扰动造成的,也就是说物品之间的体积差相对于物品体积本身而言非常微小。
以至于可以忽略不计。
所以我们直接把物品体积当作所有物品体积的最大值来算就可以了。
@data - 7@
观察数据,除了物品 1 的体积为 46972 以外,其他物品的体积都为 11743。
我们直接枚举物品 1 的摆放位置,再跑费用流,取最优值。
时间复杂度有点高,跑了 1 分钟左右才跑出来最优解。
@data - 8/9/10@
观察数据,无规律。
直接写随机化算法吧,模拟退火就是个不错的选择,代码细节我们等会儿来说。
题解:“能得多少分,全靠天注定”
@code detail@
@version - 1@
01 背包,适用于 data 3。
要不是因为这是题答,我还真不敢开这么大的数组。
这绝对会 MLE 啊。
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2000;
const int MAXV = 10000;
int dp[MAXN + 5][MAXV + 5], V[MAXN + 5];
bool pre[MAXN + 5][MAXV + 5], tag[MAXN + 5];
void get_ans(int i, int j) {
if( i == 0 ) return ;
if( pre[i][j] ) {
tag[i] = true;
get_ans(i-1, j-V[i]);
}
else {
get_ans(i-1, j);
}
}
int main() {
int N, M, K; scanf("%d%d", &N, &M);
for(int i=1;i<=N;i++)
scanf("%d", &V[i]);
scanf("%d", &K);
for(int i=1;i<=N;i++) {
int k; scanf("%d", &k);
for(int j=K;j>=V[i];j--) {
if( dp[i-1][j-V[i]] + k > dp[i-1][j] ) {
dp[i][j] = dp[i-1][j-V[i]] + k;
pre[i][j] = true;
}
else {
dp[i][j] = dp[i-1][j];
pre[i][j] = false;
}
}
}
get_ans(N, K);
for(int i=1;i<=N;i++)
puts(tag[i] ? "1" : "0");
// printf("%d\n", dp[N][K]);
}
@version - 2@
适用于 data 4, 5, 6。
快乐一换三,一份代码能得 30 分。
并且接下来的 data 7 也是直接复制过去稍微改改就可以过了。
所以应该是快乐一换三点五。
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1000;
const int MAXM = 700;
const int MAXV = MAXN + MAXM;
const int INF = (1<<30);
struct edge{
int to, flow, cap, dis;
edge *nxt, *rev;
}edges[4*MAXN*MAXM + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
struct flow_graph{
int S, T, cost, dist[MAXV + 5];
bool vis[MAXV + 5], inq[MAXV + 5];
deque<int>que;
void init() {
ecnt = &edges[0];
}
void addedge(int u, int v, int c, int w) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->dis = w, p->flow = 0;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->dis = -w, q->flow = 0;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
}
bool relabel() {
for(int i=S;i<=T;i++)
dist[i] = INF, cur[i] = adj[i];
que.push_back(S); dist[S] = 0; inq[S] = true;
while( !que.empty() ) {
int f = que.front(); que.pop_front(); inq[f] = false;
for(edge *p=adj[f];p!=NULL;p=p->nxt) {
if( p->cap > p->flow ) {
if( dist[p->to] > dist[f] + p->dis ) {
dist[p->to] = dist[f] + p->dis;
if( !inq[p->to] ) {
inq[p->to] = true;
if( !que.empty() && dist[p->to] < dist[que.front()] )
que.push_front(p->to);
else que.push_back(p->to);
}
}
}
}
}
return !(dist[T] == INF);
}
int aug(int x, int tot) {
if( x == T ) {
cost += tot*dist[T];
return tot;
}
int sum = 0; vis[x] = true;
for(edge *&p=cur[x];p!=NULL;p=p->nxt) {
if( p->cap > p->flow && !vis[p->to] && dist[x] + p->dis == dist[p->to] ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) break;
}
}
vis[x] = false;
return sum;
}
int min_cost_max_flow() {
int flow = 0;
while( relabel() )
flow += aug(S, INF);
return flow;
}
}G;
int V1[MAXN + 5], V2[MAXN + 5];
int main() {
int N, M, mx = 0; scanf("%d%d", &N, &M);
G.init(); G.S = 0, G.T = N + M + 1;
for(int i=1;i<=N;i++) {
scanf("%d", &V1[i]);
G.addedge(G.S, i, 1, 0);
mx = max(mx, V1[i]);
}
for(int i=1;i<=M;i++) {
scanf("%d", &V2[i]);
G.addedge(N + i, G.T, V2[i]/mx, 0);
}
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++) {
int k; scanf("%d", &k);
G.addedge(i, N + j, 1, -k);
}
G.min_cost_max_flow();
for(int i=1;i<=N;i++) {
int ans = N;
for(edge *p=adj[i];p!=NULL;p=p->nxt)
if( p->to != G.S && p->flow == 1 ) ans = p->to;
printf("%d\n", ans - N);
}
// printf("%d\n", -G.cost);
}
@version - 2.5@
适用于 data 7。
之所以说是 2.5,是因为这个代码的主体也是费用流,所以完全可以复制上面的代码再稍微改改。
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 500;
const int MAXM = 100;
const int MAXV = MAXN + MAXM;
const int INF = (1<<30);
struct edge{
int to, flow, cap, dis;
edge *nxt, *rev;
}edges[4*MAXN*MAXM + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
struct flow_graph{
int S, T, cost, dist[MAXV + 5];
bool vis[MAXV + 5], inq[MAXV + 5];
deque<int>que;
void init() {
ecnt = &edges[0];
for(int i=S;i<=T;i++)
adj[i] = NULL;
cost = 0;
}
void addedge(int u, int v, int c, int w) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->dis = w, p->flow = 0;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->dis = -w, q->flow = 0;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
}
bool relabel() {
for(int i=S;i<=T;i++)
dist[i] = INF, cur[i] = adj[i];
que.push_back(S); dist[S] = 0; inq[S] = true;
while( !que.empty() ) {
int f = que.front(); que.pop_front(); inq[f] = false;
for(edge *p=adj[f];p!=NULL;p=p->nxt) {
if( p->cap > p->flow ) {
if( dist[p->to] > dist[f] + p->dis ) {
dist[p->to] = dist[f] + p->dis;
if( !inq[p->to] ) {
inq[p->to] = true;
if( !que.empty() && dist[p->to] < dist[que.front()] )
que.push_front(p->to);
else que.push_back(p->to);
}
}
}
}
}
return !(dist[T] == INF);
}
int aug(int x, int tot) {
if( x == T ) {
cost += tot*dist[T];
return tot;
}
int sum = 0; vis[x] = true;
for(edge *&p=cur[x];p!=NULL;p=p->nxt) {
if( p->cap > p->flow && !vis[p->to] && dist[x] + p->dis == dist[p->to] ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) break;
}
}
vis[x] = false;
return sum;
}
int min_cost_max_flow() {
int flow = 0;
while( relabel() )
flow += aug(S, INF);
return flow;
}
}G;
int V1[MAXN + 5], V2[MAXN + 5], val[MAXN + 5][MAXM + 5];
int ans[MAXN + 5], res = 0;
int main() {
int N, M; scanf("%d%d", &N, &M);
G.S = 0, G.T = N + M + 1; G.init();
for(int i=1;i<=N;i++)
scanf("%d", &V1[i]);
for(int i=1;i<=M;i++)
scanf("%d", &V2[i]);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
scanf("%d", &val[i][j]);
for(int i=2;i<=N;i++)
for(int j=1;j<=M;j++)
G.addedge(i, N + j, 1, -val[i][j]);
for(int i=2;i<=N;i++)
G.addedge(G.S, i, 1, 0);
for(int i=1;i<=M;i++)
G.addedge(N + i, G.T, V2[i]/V1[2], 0);
G.min_cost_max_flow(); res = -G.cost;
for(int i=2;i<=N;i++) {
int flag = N;
for(edge *p=adj[i];p!=NULL;p=p->nxt)
if( p->to != G.S && p->flow == 1 ) flag = p->to;
ans[i] = flag - N;
}
for(int i=1;i<=M;i++) {
if( V2[i] < V1[1] ) continue;
G.init();
for(int j=2;j<=N;j++)
for(int k=1;k<=M;k++)
G.addedge(j, N + k, 1, -val[j][k]);
for(int j=2;j<=N;j++)
G.addedge(G.S, j, 1, 0);
for(int j=1;j<=M;j++)
if( j == i ) G.addedge(N + j, G.T, (V2[j] - V1[1])/V1[2], 0);
else G.addedge(N + j, G.T, V2[j]/V1[2], 0);
G.min_cost_max_flow();
if( res < -G.cost + val[1][i] ) {
res = -G.cost + val[1][i]; ans[1] = i;
for(int i=2;i<=N;i++) {
int flag = N;
for(edge *p=adj[i];p!=NULL;p=p->nxt)
if( p->to != G.S && p->flow == 1 ) flag = p->to;
ans[i] = flag - N;
}
}
}
for(int i=1;i<=N;i++)
printf("%d\n", ans[i]);
// printf("%d\n", res);
}
@version - 3@
适用于 data 2, 8, 9, 10。
题答的本质其实是模拟退火和找规律。
具体来说,我们生成物品的一个排列,然后贪心地将物品依次“尝试”放入 1~M 个背包,计算当前取得的最优值。
用模拟退火来生成排列就是一个很经典的应用了。
#include<cmath>
#include<ctime>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int MAXN = 1000;
const int MAXM = 300;
const int INF = (1<<30);
int V1[MAXN + 5], V2[MAXN + 5], val[MAXN + 5][MAXM + 5];
int ans[MAXN + 5], tmp[MAXN + 5], nw[MAXN + 5], res = 0, N, M;
int get_ans() {
int ret = 0, j = 1, re = V2[1];
for(int i=1;i<=N;i++) {
if( j > M ) tmp[nw[i]] = 0;
else if( re >= V1[nw[i]] ) {
tmp[nw[i]] = j, re -= V1[nw[i]];
ret += val[nw[i]][j];
}
else j++, i--, re = V2[j];
}
return ret;
}
int real_ans[MAXN + 5], real_res = 0;
int main() {
srand(time(NULL));
freopen("drawer8.in", "r", stdin);
scanf("%d%d", &N, &M);
for(int i=1;i<=N;i++)
scanf("%d", &V1[i]);
for(int i=1;i<=M;i++)
scanf("%d", &V2[i]);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
scanf("%d", &val[i][j]);
while( true ) {
for(int i=1;i<=N;i++)
nw[i] = i;
random_shuffle(nw + 1, nw + N + 1); res = get_ans();
for(double T=1E12;T>=1E-5;T*=0.986) {
int x = rand() % N + 1, y = rand() % N + 1;
swap(nw[x], nw[y]);
if( get_ans() > res || 1.0/(1 + exp((res - get_ans())/T)) >= 1.0/rand() ) {
for(int j=1;j<=N;j++)
ans[j] = tmp[j];
res = get_ans();
}
else swap(nw[x], nw[y]);
}
if( res > real_res ) {
real_res = res;
for(int i=1;i<=N;i++)
real_ans[i] = ans[i];
FILE *f = fopen("drawer8.out", "w");
for(int i=1;i<=N;i++)
fprintf(f, "%d\n", real_ans[i]);
printf("%d\n", real_res);
}
}
}
@details@
最后三个点是 4 + 2 + 1,因为我跑的时间比较短所以应该可以跑出更好的答案。等会儿我再去试试吧。
估计如果在真实考场上看不出来第 6 个点是微小扰动……应该只会有 70 分左右的分数吧。
话说我测出来同时跑 data 8, 9, 10 CPU 消耗 75 %左右,再同时跑 data 2 CPU 差不多就炸掉了。
看着任务管理器中那个 CPU 消耗率 100% 突然有种莫名的成就感?
@loj - 2288@「THUWC 2017」大葱的神力的更多相关文章
- LOJ 2288「THUWC 2017」大葱的神力
LOJ 2288「THUWC 2017」大葱的神力 Link Solution 比较水的提交答案题了吧 第一个点爆搜 第二个点爆搜+剪枝,我的剪枝就是先算出 \(mx[i]\) 表示选取第 \(i \ ...
- @loj - 2289@「THUWC 2017」在美妙的数学王国中畅游
目录 @description@ @solution@ @accepted code@ @details@ @description@ n 个点编号 0 到 n-1,每个点有一个从 [0,1] 映射到 ...
- @loj - 2290@ 「THUWC 2017」随机二分图
目录 @description@ @solution@ @accepted code@ @details@ @description@ 一个左右各 n 个点的二分图,图中的边会按照一定的规律随机出现. ...
- 「THUWC 2017」随机二分图
「THUWC 2017」随机二分图 解题思路 : 首先有一个 \(40pts\) 的做法: 前 \(20pts\) 暴力枚举最终的匹配是怎样的,check一下计算方案数,后 \(20pts\) 令 \ ...
- 「LOJ 2289」「THUWC 2017」在美妙的数学王国中畅游——LCT&泰勒展开
题目大意: 传送门 给一个动态树,每个节点上维护一个函数为$f(x)=sin(ax+b)$.$f(x)=e^{ax+b}$.$f(x)=ax+b$中的一个. 支持删边连边,修改节点上函数的操作. 每次 ...
- 【LOJ】#2290. 「THUWC 2017」随机二分图
题解 看了一眼觉得是求出图对图统计完美匹配的个数(可能之前做过这样模拟题弃疗了,一直心怀恐惧... 然后说是统计一下每种匹配出现的概率,也就是,当前左边点匹配状态为S,右边点匹配状态为T,每种匹配出现 ...
- 【LOJ】#2289. 「THUWC 2017」在美妙的数学王国中畅游
题解 我们发现,题目告诉我们这个东西就是一个lct 首先,如果只有3,问题就非常简单了,我们算出所有a的总和,所有b的总和就好了 要是1和2也是多项式就好了--其实可以!也就是下面泰勒展开的用处,我们 ...
- @loj - 2977@ 「THUSCH 2017」巧克力
目录 @description@ @solution@ @accepted code@ @details@ @description@ 「人生就像一盒巧克力,你永远不知道吃到的下一块是什么味道.」 明 ...
- LOJ #2978「THUSCH 2017」杜老师
听说LOJ传了THUSC题赶紧上去看一波 随便点了一题都不会做想了好久才会写暴力爆了一发过了... LOJ #2978 题意 $ T$次询问,每次询问$ L,R$,问有多少种选取区间中数的方案使得选出 ...
随机推荐
- python使用cPickle模块序列化实例
python使用cPickle模块序列化实例 这篇文章主要介绍了python使用cPickle模块序列化的方法,是一个非常实用的技巧,本文实例讲述了python使用cPickle模块序列化的方法,分享 ...
- 【linux配置】Redhat6.5基础配置指南
Redhat6.5基础配置指南 本文针对鄙人在工作中常用系统配置加以说明,通常公司用于生产和测试的服务器基本上都不能连接外网,需要针对刚刚安装好的系统做一系列的常用配置. 一.常用基础系统配置 1.关 ...
- 锋利的jQuery学习笔记之jQuery选择器
在介绍jQuery选择器之前,先简单介绍一下CSS选择器---> 一.CSS选择器 常见的CSS选择器有以下几种: 选择器 语法 描述 示例 标签选择器 E{CSS规则} 以文档元素为选择符 t ...
- E浏览器常见的9个css Bug以及解决办法
我们在浏览网页的时候经常看见这样的现象:某个网页在IE6浏览器中打开很正常,但是在IE8里面打开可能完全变形了.或者也有可能出现完全相反的现象.这让Web程序员及设计师往往为了其CSS在各个IE版本下 ...
- mysql优化-数据库设计基本原则
mysql优化-数据库设计基本原则 一.数据库设计三范式 第一范式:字段具有原子性 原子性是指数据库的所有字段都不可被再次划分,如下表就不满足原子性,起点与终点 字段就可被拆分为起点与终点两个字段. ...
- CentOS 7安装与配置Tomcat8
1.下载安装包并上传服务器 2.解压 tar -zxvf apache-tomcat-8.5.16.tar.gz -C /opt/java 3.启动 cd /opt/java/apache-tomca ...
- 数据库设计 ch.7
数据库建设的基本规律 三分技术 七分管理 十二分基础数据 阶段 需求分析阶段 概念设计阶段 逻辑设计阶段 物理设计阶段 数据库实施阶段 数据库维护阶段 1 需求分析 2 概念设计 形成概念模型 3 逻 ...
- 【 USACO11JAN】 利润 【洛谷P3009】
P3009 [USACO11JAN]利润Profits 题目描述 The cows have opened a new business, and Farmer John wants to see h ...
- JavaScript--jquery.min.js文件
/*! jQuery v1.12.3 | (c) jQuery Foundation | jquery.org/license */ !function(a,b){"object" ...
- selenium(6):通过多种定位方式还是不能成功定位的原因
场景:在成功修改密码后,会弹出一个修改成功的提示.通过id.xpath.class.css方式定位后,执行到这一步时候,就会出现错误. 原因:仔细检查了下代码,发现在提交修改的操作到修改成功的提示之间 ...