CF_数学、技巧、贪心1

LINK

有n=500个人,标号为[1, 2, 3, ..., n],最开始 他们的wth都是0
发生[1,2] [1,3] [1,4] ... [1,n] | [2,3] [2,4] ... [2,n] 
	[3,4] [3,5] [3,6] ... [3,n] | ... | [n-1,n]
 总共是(n*(n-1)/2)场战斗,这些场战斗的顺序 就是上面写的顺序
 每场战斗,你可以去设定 谁赢谁输、或平局
 对于某一场战斗[a,b] (a<b)1表示a赢,-1表示b赢,0表示平手。
 谁赢谁的wth += 3, 如果是平局 则两人的wth都 += 1
即你最终需要输出(n*(n-1)/2)个数字 (1/-1/0),要保证:
	1,最终这n个人 他们的wth均相同!!  (可以证明,一定可以)
	2,这n*(n-1)/2场战斗中,发生'平局'的次数 尽可能的少!!


这些场战斗,你很容易会认为: 1参与(n-1)场,2参与(n-2)场,..., n-1参与1场
这是错的!!!   这是双向边,而不是单向边!!
你画着这个图,会发现:  这其实是一个“完全图”
	即每个点,都是与其他n-1个点有连接边。  每个点的度数均相同 = n-1
	即,其实每个人 都是一样的!!!

令tot = n*(n-1)/2 场战斗
1, 最终这n个人的wth相同,自然我们让所有战斗都是平局,最终每个人的wth都是(tot)
	但不要忘了,我们需要让'平局'的个数 尽量的少!!!
	即核心是'我们要判断,是否可以 {不使用平局},而让所有人的wth相同' !!
	'什么意思呢?  这tot次战斗,不使用平局。 即每次都是“赢”  '
	' 即每场战斗都会导致: 一个人+=3, 另一个人不变。 所以可以看出是: 赢 '
	' 如果说,不使用平局。 则要满足: tot % n == 0 (即这tot次赢,可以平分n个人)'
	
	I: n为偶数      '核心是: 是否可以不使用平局,而让所有人的wth相同'
		比如n=4, tot=6。  即tot % n != 0
		意味着,如果你不使用平局,意味着这tot=6次战斗,会带来6次胜利。
		而这6次胜利,无法平分到这n=4个人上!!!
		因此,这种情况,一定需要去借助'平局'。   暂且先跳过,一会再处理
	II: n为奇数     '也是,首要去判断: 是否可以不使用平局 '
		比如n=7, tot=21.   即tot % n == 0   (n*(n-1)/2 = (k) * n, 因为n-1是偶数)
		所以, 这种情况 是可以不使用平局的!!!
		即,如何让这tot个 平均到这 n个人身上。
		有很多方法: 
		FOR(i, 1, n, 1){
			f = true;
			FOR(j, i + 1, n, 1){
				if(f){ PD(1); }	   
				else{  PD(-1); }
				f = !f;
			} / 即让i去遍历[i+1, i+2, ..]时,交替的来 (可以证明,最终是平分的)
		} / 当然,不交替也是可以的。 比如你贪心,配合cont计数, 
		 / 让i去遍历[i+1, i+2..]时,只要cont[i]<avg,就让i去贪心选择i+1, i+2, ...
		 / 注意,必须要用cont来计数。  因为当前的i,前面会有元素 让i赢!!!

关键是看: n为偶数的情况!!!
	n=4, tot=6		n=6, tot=15		n=8, tot=28
	tot=(n*(n-1)/2),距离 n的倍数,最近的是: n*(n-2)/2
	因为,n*(n-2)/2 % n == 0。   即让tot -= (n/2)
	即,n/2次是平局。   T = (tot - n/2)次是赢。
	最终,  这n个人的wth = 3*T + (n/2)。  都是相同的。
	我们需要去证明:  n/2次是平局,是否可以把T 平分到n个人身上!!!
	 (如果可以,n/2次平局 一定是最小的了!!! 因为T=(tot - (<n/2)) 都不是 n的倍数 )
1, 我们很容易会想到一种做法:
	这n/2次平局是: (1,n) (2,n-1) (3,n-2) (4,n-3) ... 
	然后,你会很容易想到,向之前的处理一样,让每个i 交替/贪心的选
	  '你非常容易会这样去做.....'	
		FOR(i, 1, n, 1){
			FOR(j, i + 1, n, 1){
				if( (i + j) == (1 + n) ){
					PD(0); SP;
					continue;
				}
				if(cont[i] == ma){  / 肯定是j赢
					PD(-1); SP; cont[j] ++;
					continue;
				}
				if(cont[j] == ma){	/ 注意,这里非常关键!!!
					PD(1); SP; cont[i] ++;
					continue;
				}
				PD(1); SP; cont[i] ++;
			}	/ 你非常容易的,就写出这个代码!!!  看似非常天衣无缝...
		} / 但,其实有可能: cont[i]==cont[j]==ma!!!
		/ 因为,你的i从小到大,每次都是: [i+1, i+2, ..., n-1, n]
		/ 靠近i的(先让i赢),然后剩下的(靠近n的)(再让i输)
		/ '每个i,都是如此!!', 你仔细想想: 其实你会让(靠近n的) 赢很多次!!
		/ 因为,对于i=1,2,3,4.... 你几乎都是让[i,n]选择的是: n赢(因为i已经赢上限了)
		/ '当然,你肯定很难想到这里。'
		/ ' 最好就是: 写一些测试[4,6,8,10,..],你很快就会发现: 这个算法是错的!! ' 
	基于,n最终会很大!! (我们修改策略: 每次不是贪心,而是“交替”!!!)
	'但事实证明,也不是直接的交替 也是错的.... '
	'而是,带有特判的交替!!! 比如说,对于某个i,当前遍历到j,cont[i]距离上限还差need个'
	' 现在还有[n-j+1]个元素,其中可能有(平局的)也可能没有,假如去掉平局的,有rest个'
	' 假如说,rest == need, 那你可能就不能交替了!! 必须一直的去赢。) '
	'   当然,如果是 rest < need,说明算法错误 , 但不会发生,因为是交替,比较平均 '

if(n & 1){
	VE<int> cont(n + 1, 0);
	int ma = n / 2;   ' 最终每个人,赢的次数;   n&1,没有平局 '
	FOR(i, 1, n, 1){
		bool f = true;
		FOR(j, i + 1, n, 1){
			if(f){ PD(1); }		' 直接交替。 最终,每个人的wth  均为 ma '
			else{ PD(-1); } 
			f = !f;
		}
	}
}else{
	int ma = (n/2) - 1;     ' 最终每个人是: ma次赢, (n/2)次平局 '
	VE<int> cont(n + 1, 0);
	FOR(i, 1, n, 1){
		bool f = true;
		FOR(j, i + 1, n, 1){
			if( (i + j) == (1 + n) ){ ' 平局 '
				PD(0); SP;
				continue;
			}
			if(cont[i] == ma){ 
				PD(-1); SP; cont[j] ++;
				continue;
			}
			if(cont[j] == ma){ '不会发生: cont[i]=cont[j]=ma。'
				PD(1); SP; cont[i] ++;    ' 因为,我们使用了:交替 '
				continue;
			}
			
			int rest;  ' 重点!!! 你不可以直接的交替!!! '
			if( (i + j) < (1 + n) ){
				rest = n - j;
			}else{
				rest = n - j + 1;
			} ' rest为: [j, j+1, ..., n]中,除去(必须平局的)后,总对局数 '
			// 不会出现 (rest < (ma-cont[i]))的情况。
			// (ma - cont[i])为: 当前的i 还需要赢几次
			if(rest == (ma - cont[i])){  '重点!!! 这里不可以再交替了... '
				PD(1); SP;
				cont[i] ++;
			}else if(rest > (ma - cont[i])){   '重点!!! 有空闲的机会,就交替!!! '
				if(f){ PD(1); SP; cont[i] ++; }
				else{ PD(-1); SP; cont[j] ++; }
				f = !f;
			}
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值