zoukankan      html  css  js  c++  java
  • @loj


    @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
    ", 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
    ", ans - N);
    	}
    //	printf("%d
    ", -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
    ", ans[i]);
    //	printf("%d
    ", 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
    ", real_ans[i]);
    			printf("%d
    ", real_res);
    		}
    	}
    }
    

    @details@

    最后三个点是 4 + 2 + 1,因为我跑的时间比较短所以应该可以跑出更好的答案。等会儿我再去试试吧。
    估计如果在真实考场上看不出来第 6 个点是微小扰动……应该只会有 70 分左右的分数吧。

    话说我测出来同时跑 data 8, 9, 10 CPU 消耗 75 %左右,再同时跑 data 2 CPU 差不多就炸掉了。
    看着任务管理器中那个 CPU 消耗率 100% 突然有种莫名的成就感?

  • 相关阅读:
    从汇编的角度看待const与#define
    从汇编的角度看待变量类型与sizeof的机制
    按字节对齐分析
    堆内存和栈内存的探索
    string源码实现分析
    string源码分析 ——转载 http://blogs.360.cn/360cloud/2012/11/26/linux-gcc-stl-string-in-depth/
    开始了
    atoi函数的实现——面试
    new与malloc的区别,以及内存分配浅析
    [C]C语言中EOF是什么意思?
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10232002.html
Copyright © 2011-2022 走看看