首页 > 其他 > 详细

Kick Start 2019 Round G Problem C shifts 两种解法(SOS DP, Segment tree)

时间:2019-10-29 01:55:51      阅读:83      评论:0      收藏:0      [点我收藏+]

技术分享图片

第一种解法:分别将A和B对于n个shift的所有组合情况计算出happy值,对于happy value>=H的组合,分别保存在seta和setb中。枚举seta中的每个组合 i,找到setb中能与 i 进行或运算得到(1<<n) -1的组合 j,这表明A的一个组合i :happy value>=H, B的一个组合j: happy value >=H,且A的这个组合和B的这个组合满足n shift都至少有1人值班。(这里运用了一个原理:如果 I |J =( 1<<n) - 1,那么J的super set K,也满足I | K = (1<<n)-1), 用SOS DP计算某个组合有多少个super set.

注:进行DP的二维数组num, 可以优化为一维数组,为了便于理解递推关系,这里用的二维。

关于SOS DP,这里有一篇比较详细的文章:http://codeforces.com/blog/entry/45223

与文章中不同的是,这里是求一个组合的super set(父集), 而文章中是介绍求一个组合(集合)的subset(子集)。不论是父集还是子集,其内在的思想是一样的:例如101010111,第4位是0,那么这个组合的父集的第4位可以是0或1;第3位是1,那么这个组合的父集第3位只能是1。(子集恰好相反,这个组合的子集第4位只能是0,而子集第3位可以是0或1)。

num[i][mask] 是表示与组合mask“前i位不尽相同,高位相同”的子集的数目.

 1 import java.util.Scanner;
 2 import java.util.HashSet;
 3 public class GCJ_KI2019G_C5{  //SOS DP
 4     public static void main(String[] args){
 5         Scanner in = new Scanner(System.in);
 6         int T = in.nextInt();        
 7         for(int cas = 1; cas <= T; cas++){
 8             int n = in.nextInt();
 9             long h = in.nextLong();
10             long[] a = new long[n];
11             long[] b = new long[n];
12             long sumA = 0L;   long sumB = 0L;
13             for(int i = 0; i < n; i++){
14                 a[i] = in.nextLong();
15                 sumA += a[i];
16             }
17             for(int i = 0; i < n; i++){
18                 b[i] = in.nextLong();
19                 sumB += b[i];
20             }
21             if(sumA < h || sumB < h){
22                 System.out.println("Case #"+cas+": "+0);
23                 continue;
24             }
25 
26             HashSet<Integer> seta = new HashSet<>();
27             recur(a,0,h,0L,0,seta);
28             HashSet<Integer> setb = new HashSet<>();
29             recur(b,0,h,0L,0,setb); 
30 
31             int[][] num = new int[n+1][1<<n];
32             int x = (1<<n) - 1;
33             for(int v: setb)
34                 num[0][v] = 1; 
35             
36             for(int i = 1; i <= n; i++){                 //SOS DP
37                 for(int mask = 0; mask <= x; mask++){
38                     num[i][mask] = num[i-1][mask];
39                     if((mask & (1<<(i-1))) == 0)
40                         num[i][mask] += num[i-1][mask|1<<(i-1)];
41                 }
42             }
43                     
44             long ans = 0l;            
45             for(int v: seta){
46                 ans += num[n][x-v];  
47             }        
48             System.out.println("Case #"+cas+": "+ans);
49         }
50     }
51     
52     static void recur(long[] arr, int p, long h, long accu, int bitmask, HashSet<Integer> set){    
53         if(p == arr.length){                  
54             if(accu >= h){ 
55               set.add(bitmask);
56             }
57             return;
58         }
59         recur(arr,p+1,h,accu, bitmask,set);
60         recur(arr,p+1,h,accu+arr[p], bitmask|(1<<p),set);
61     }
62 }

*******************************************分割线***********************************************

第二种解法:

将n个shift分成两段,0~n/2与n/2+1~n-1

每一段都会形成若干对happy value,对于第一段中的happy value pair,若第二段中存在happy value pair,能够满足pair1.first + pair2.first >= h且 pair1.second + pair2.second >= h, 那么这是一个有效的解。

对于第一段中的一个pair, 运用线段树可以较快的找出第二段中满足以上条件的pair的数目。

import java.util.Scanner;
import java.util.ArrayList;
import java.util.Collections;

public class Solution{
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
		int T = in.nextInt();	
		for(int cas = 1; cas <= T; cas++){
			int n = in.nextInt();
			int h = in.nextInt();
			long[] a = new long[n];
			long[] b = new long[n];
			long sumA = 0L;   long sumB = 0L;
			for(int i = 0; i < n; i++){
				a[i] = in.nextLong();
				sumA += a[i];
			}
			for(int i = 0; i < n; i++){
				b[i] = in.nextLong();
				sumB += b[i];
			}
			if(sumA < h || sumB < h){
				System.out.println("Case #"+cas+": "+0);
				continue;
			}
			
			ArrayList<Pair> list1 = new ArrayList<>();
			recur(a,b,0,n/2,0L,0L,list1,h);
			ArrayList<Pair> list2 = new ArrayList<>();
			recur(a,b,n/2+1,n-1,0L,0L,list2,h);
			Collections.sort(list1);
			Collections.sort(list2);
			SegTree tree = new SegTree(0,h);
			long ans = 0;			
			int p = list2.size() - 1;
			for(Pair pair: list1){
				while(p >= 0 && list2.get(p).fir >= h - pair.fir){
					tree.add(list2.get(p).sec);
					p--;
				}
				ans += tree.get(h - pair.sec, h);
			}
			System.out.println("Case #"+cas+": "+ans);				
		}
	}
	
	static class SegTree{
		int low, high;
		SegTree left, right;
		int sum = 0;
		public SegTree(int a, int b){
			low = a;  high = b;
		}
		public void add(int v){
			sum++;
			if(low == high)
				return;
			int m = low + (high-low)/2;
			if(v <= m){
				if(left == null)
					left = new SegTree(low,m);
				left.add(v);
			}
			else{
				if(right == null)
					right = new SegTree(m+1,high);
				right.add(v);
			}
		}

		public int get(int a, int b){
			if(a <= low && b >= high)
				return sum;
			if(a > high || b < low)
				return 0;
			int res = 0;
			if(left != null)
				res = left.get(a,b);
			if(right != null)
				res += right.get(a,b);
			return res;
		}
	}
	static class Pair implements Comparable<Pair>{
		int fir, sec;
		public Pair(int a, int b){
			fir = a;  sec = b;
		}
		public int compareTo(Pair pair){
			return Integer.compare(fir, pair.fir);
		}
	}

	static void recur(long[] a, long[] b, int p, int end, long accuA, long accuB, ArrayList<Pair> list,int h){
		if(p > end){
			list.add(new Pair((int)Math.min(h,accuA), (int)Math.min(h,accuB)));
			return;
		}
		recur(a,b,p+1,end,accuA+a[p],accuB,list,h);
		recur(a,b,p+1,end,accuA, accuB+b[p],list,h);
		recur(a,b,p+1,end,accuA+a[p],accuB+b[p],list,h);
	}
}

  

 

Kick Start 2019 Round G Problem C shifts 两种解法(SOS DP, Segment tree)

原文:https://www.cnblogs.com/black-fish/p/11756428.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!