题目链接
A. Card Exchange
题意
给你n个牌,每个牌都有一个数字。
每次你可以选择恰好k个相同数字的牌, 然后将他们替换为任意的k-1张牌。(这k-1张牌的上的数字任意)
问在经过一些操作后,最终能剩下的牌的数量最少是多少。
思路
最终剩下牌的数量 = n - 最多执行的操作次数。
所以我们很明显地发现,如果起初不存在相同的k个数,那么就无法进行操作,直接输出n即可。
给出结论,如果可以进行第一次操作,那么就可以使得最终牌的数量变为k - 1。
下面是证明:
我们设当前有n张牌 , 在满足”存在相同的k张牌“的前提下, 有如下两种情况:
- 只有k张牌,且全部相同。那么再进行一次操作就可以实现最终剩下k-1张牌。
- 除了这k张牌之外,还有其他的牌(设他的值为num),那么我们就可以选择k-1张num来替换起初的k张相同牌, 这样就变为了总数为n-1的,仍然满足”存在相同的k张牌“的状态。最终会逐渐转化为第一种情况,并变为只剩k-1张牌的情况。
按照以上两种情况进行考虑,就可以保证最终会剩下k-1张牌。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include<bits/stdc++.h> using namespace std; #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define int long long #define rep(i,l,r) for(int i = l;i<=r;i++) #define per(i,r,l) for(int i = r;i>=l;i--) const int INF = 0x3f3f3f3f3f3f3f3f; typedef pair<int,int> PII; void solve(){ int n,k; cin>>n>>k; vector<int> cnt(105); rep(i,1,n){ int num;cin>>num; cnt[num]++; } bool f = 0; rep(i,1,100){ if(cnt[i] >= k ) f = 1; } if(!f){ cout<<n<<endl; return ; }else{ cout<<min(n,k-1)<<endl; return ; } } signed main(){ int T = 1; cin>>T; while(T--){ solve(); } return 0; }
|
B. Rectangle Filling
题意
有一个n*m的表格,涂满了黑白两色。
规定如下操作: 每次选出两个相同颜色的格子, 让”以他们为对角线的矩形区域“ 全部变为和选择方格同一种颜色。
在进行一些次数的操作后,能否使得整个n*m的表格全部变为某一种颜色。
思路
若四条边中同时存在某一种颜色,那么就可以将整个表格变为同一个颜色,否则就不可以。
比如有如下样例:
我们可以将上面的表格变为白色, 只需从四个边中各自任意选出一个白色块W(可以重复选择某一块,如(3,4)这个W块,即作为靠下的边,也作为靠右边。)
我们选出至多四个块, 然后将他们作为操作时要选择的块,就可以使得整个表格变为这个颜色。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include<bits/stdc++.h> using namespace std; #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define int long long #define rep(i,l,r) for(int i = l;i<=r;i++) #define per(i,r,l) for(int i = r;i>=l;i--) const int INF = 0x3f3f3f3f3f3f3f3f; typedef pair<int,int> PII; void solve(){ int n,m; cin>>n>>m; int bl,br,bu,bd; int wl,wr,wu,wd; bl = bu = wl = wu = 0x3f3f3f3f; br = bd = wr = wd = -0x3f3f3f3f; rep(i,1,n){ rep(j,1,m){ char ch;cin>>ch; if(ch == 'B'){ bl = min(bl,j);br = max(br,j); bu = min(bu,i);bd = max(bd,i); }else{ wl = min(wl,j);wr = max(wr,j); wu = min(wu,i);wd = max(wd,i); } } } if(bl == 1 && br == m && bu == 1&&bd == n){ cout<<"YES\n";return ; } if(wl == 1 && wr == m && wu == 1&&wd == n){ cout<<"YES\n";return ; } cout<<"NO\n"; return ; } signed main(){ int T = 1; cin>>T; while(T--){ solve(); } return 0; }
|
C. Everything Nim
题意
有n个堆, 第$i$个堆初始有$a_i$ 个石头, $Alice$和$Bob$轮流进行操作,Alice先手,每次操作如下:
选择一个数x,保证$x > 0 $且x不大于 任何非空堆 的剩余石头数量。
从所有的非空堆中都拿去x个石头。
最终无法操作(即所有堆全部为空)者会输掉比赛。
二人都选取最优策略,问谁会胜利
思路
我们先将堆按照从小到大的顺序排序, 并且去重。我们会从小到大地不断清空每一堆。
情况一:当某一堆和前一堆的数量只差1的时候, 清空这堆时别无他法,只能设置x为1,并拿走这堆的最后一个,(因为在清空前一个堆的时候已经将这一堆拿到只剩下1个石头)
情况二:如果这一堆和上一堆的数量差大于1,那么就可以选择
- 全部拿走,即让对手去清空下一堆。
- 拿到只剩一个, 即让自己去清空下一堆。
可以发现这两者会正好对应相反的结果, 而我们又可以从中任选一个,所以就一定会获胜。
于是就有如下结论:第一个遇到情况二的人会获胜。
注意考虑第一堆的数量情况,如果第一堆的数量大于2,那么Alice起手变获胜
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include<bits/stdc++.h> using namespace std; #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define int long long #define rep(i,l,r) for(int i = l;i<=r;i++) #define per(i,r,l) for(int i = r;i>=l;i--) const int INF = 0x3f3f3f3f3f3f3f3f; typedef pair<int,int> PII; void solve(){ int n; cin>>n; vector<int> a(n+5); rep(i,1,n){ cin>>a[i]; } sort(a.begin()+1,a.begin()+1+n); bool f = 1; int cnt = 0; for(int i = 0;i<n;i++){ if(a[i] == a[i+1]) continue; if(a[i] == a[i+1] - 1){ f ^= 1; }else{ f^= 1; break; } } if(f)cout<<"Bob\n"; else cout<<"Alice\n"; } signed main(){ int T = 1; cin>>T; while(T--){ solve(); } return 0; }
|
D. Missing Subsequence Sum
题意
给出n和k, 请你构造一个长度不大于25的数组, 保证:
- a中不存在某个子序列的和为k
- 对于任意$1\le v \le n , v \not= k$ ,都有 a中存在某个子序列的和为k
思路
k将$[1,n]$ 分割为了两部分, $[1,k-1]$和 $[k+1,n]$ ,我们分别构造这两部分。
对于$[1,k-1]$部分, 我们使用二进制数组来构造$[1,2,4,…,2^p,Q]$ , 这些数的组合可以表示出 $[1,2^{p+1}-1+Q]$ 之间的任意数,于是我们设置$2^{p+1}$ 为”小于等于k的 最大的二次幂数“ , 即$2^{p+1} \le k $且$ 2^{p + 2} > k$ 。而$Q = k - 2^{p+1}$ 。 这样我们就可以构造出所有的$[1,k-1]$ 的数。
对于$[k+1,n]$ 部分,我们先选两个数$k+1,k+2$ 。 此时我们可以表示出$[k+1,k+2]$之间的数,
我们设能表示的最大的数是mx, 那么下一个要表示的数$nxt$就是$mx + 1$ ,我们选择数$nxt -(k+1)$ 作为下一个要加的数,这时候就可以将”可表示区域“ 拓宽为$[k+1,mx+nxt-(k+1)]$ ,但是我们发现,如果$num = nxt - (k + 1) \le k$ 那么会导致k被$num + (k - num)$ 表示出来,就出现了问题,如果出现了这个问题,我们就不能直接将$nxt - (k+1)$ 插入答案序列中,而是选择换成$nxt$ 直接插入序列中。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<bits/stdc++.h> using namespace std; #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define int long long #define rep(i,l,r) for(int i = l;i<=r;i++) #define per(i,r,l) for(int i = r;i>=l;i--) const int INF = 0x3f3f3f3f3f3f3f3f; typedef pair<int,int> PII; void solve(){ int n,k; cin>>n>>k; int mx = 0; int nx = 1; vector<int> ans; while(mx + (mx + 1) < k - 1){ ans.push_back(mx+1); mx += mx + 1; } ans.push_back(k - 1 - mx); mx = k - 1; ans.push_back(k+1); ans.push_back(k+2); mx = k+2; while(mx < n){ int tt = mx-k; if(tt <= k){ ans.push_back(mx+1); mx += mx+1; continue; } ans.push_back(tt); mx += tt; } cout<<ans.size()<<endl; for(auto p : ans){ cout<<p<<" "; } puts(""); } signed main(){ int T = 1; cin>>T; while(T--){ solve(); } return 0; }
|