有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;
}
}
}
CF_数学、技巧、贪心1
最新推荐文章于 2022-11-29 10:33:34 发布