【题解】Codeforces Round 811 Div.3(CF1714)
总体来说没有全切掉就比较遗憾,主要还是手速比较慢。
A.Everyone Loves to Sleep
题目描述:
题目分析:
考虑闹钟的数量很少,所以直接暴力计算出睡到每一个闹钟响的时间,然后取最小值就好了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct node{
int h,m;
node(){}
node(int _h,int _m){
h = _h,m = _m;
}
};
bool operator < (node l,node r){
if(l.h != r.h) return l.h < r.h;
return l.m < r.m;
}
bool operator == (node l,node r){
return l.h == r.h && l.m == r.m;
}
node operator - (node l,node r){
node tmp;
tmp.h = l.h - r.h;
if(l.m < r.m)
l.m += 60,tmp.h--;
tmp.m = l.m - r.m;
return tmp;
}
node operator + (node l,node r){
node tmp;
tmp.h = l.h + r.h;
tmp.m = l.m + r.m;
tmp.h += tmp.m / 60;
tmp.m %= 60;
return tmp;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int t;
cin>>t;
while(t--){
int n;
node a;
cin>>n;
cin>>a.h>>a.m;
node ans = node(100,100);
for(int i=1; i<=n; i++){
node tmp;
cin>>tmp.h>>tmp.m;
node h = node(24,0);
if(a < tmp)
ans = min(ans,tmp - a);
else if(tmp < a)
ans = min(ans,(h - a) + tmp);
else
ans = node(0,0);
}
printf("%d %d\n",ans.h,ans.m);
}
return 0;
}
这里就是封装了一个结构体
B.Remove Prefix
题目描述:
题目分析:
我们要使得某一个后缀不含有重复元素,也就是可以理解为我们将每个元素的倒数第二次出现的地方及之前删掉,这样所有的取最大值就是答案。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5+5;
int a[MAXN];
vector<int> v[MAXN];
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++){
scanf("%d",&a[i]);
v[a[i]].push_back(i);
}
int ans = 0;
for(int i=1; i<=n; i++){
if(v[i].size() > 1){
ans = max(ans,v[i][v[i].size()-2]);
}
}
printf("%d\n",ans);
for(int i=1; i<=n; i++) v[i].clear();
}
return 0;
}
C.Minimum Varied Number
题目描述:
题目分析:
为了使得这个数最小我们肯定是贪心地从大到小选取数,因为这样可以保证位数最小。然后能选则选,最后按从小到大的顺序输出就好了,因为显然这样是选择这些数的前提下的最小的方案。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
int s;
cin>>s;
stack<int> st;
for(int i=9; i>=1; i--){
if(s - i >= 0) st.push(i),s-=i;
}
while(!st.empty()){
int now = st.top();st.pop();
printf("%d",now);
}
printf("\n");
}
return 0;
}
D.Color with Occurrences
题目描述:
题目分析:
因为 \(|s|,|t|\) 都很小,所以我们可以考虑直接暴力处理出 \(t\) 中能放每一个 \(s\) 的位置,这样假设我们在 \(x\) 位置放置一个 \(s_i\),那么就相当于这一个 \(s_i\) 能完全覆盖 \([x,x + |s_i| - 1]\)。
那么这个问题就显然地转化为了一个最少线段覆盖的问题,而且显然线段最多只有 \(1000\) 条。
对于这个问题我们可以动态维护 \([l,r]\) 代表我们现在已经覆盖的区间,然后每次选择左端点在 \([l,r]\) 内的线段,对他们的右端点取一个最大值,这样直到所有的符合条件的线段都考虑了之后,我们得到的最大值就是当前能放置的最优的一条线段。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100000;
struct node{
int l,r,pos;
node(){}
node(int _l,int _r,int _pos){
l = _l,r = _r,pos = _pos;
}
}a[MAXN],ans[MAXN];
int sz_t,cnt,sz_s[11];
char t[MAXN],s[11][MAXN];
bool cmp(node l,node r){
return l.l < r.l;
}
void work(){ //求解最小线段覆盖
sort(a+1,a+cnt+1,cmp);
int now_r = 0;
int tmp_r = 0;
int sum = 0;
int tot = 0;
bool tag = true;
node now_ans;
for(int i=1; i<=cnt; i++){
if(a[i].l <= now_r + 1) {
if(a[i].r > tmp_r){
tmp_r = a[i].r;
now_ans = a[i];
}
}
else{
sum++;
ans[++tot] = now_ans;
now_r = tmp_r;
if(a[i].l > now_r + 1){
tag = false;
break;
}
else{
if(a[i].r > tmp_r){
tmp_r = a[i].r;
now_ans = a[i];
}
}
}
}
if(!tag){
printf("-1\n");
return;
}
else{
if(now_r < sz_t){ //注意这种情况
now_r = max(now_r,tmp_r);
sum++;
ans[++tot] = now_ans;
}
if(now_r < sz_t){
printf("-1\n");
return;
}
printf("%d\n",sum);
for(int i=1; i<=tot; i++){
printf("%d %d\n",ans[i].pos,ans[i].l);
}
}
}
bool check(int pos,int j){ //判断能否在 pos 的位置放置 s[j]
for(int i = pos; i<=pos + sz_s[j]-1; i++){
if(t[i] != s[j][i - pos + 1]) return false;
}
return true;
}
int main(){
int q;
cin>>q;
while(q--){
cnt = 0;
scanf("%s",t+1);
sz_t = strlen(t + 1);
int n;
cin>>n;
for(int i=1; i<=n; i++){
scanf("%s",s[i] + 1);
sz_s[i] = strlen(s[i] + 1);
}
for(int i=1; i<=sz_t; i++){ //将所有的线段摘出来
for(int j=1; j<=n; j++){
if(i + sz_s[j] - 1 <= sz_t && check(i,j))
a[++cnt] = node(i,i + sz_s[j] - 1,j);
}
}
work();
}
return 0;
}
E.Add Modulo 10
题目描述:
题目分析:
我们通过打表可以发现,数可以分为两类:
- 以 \(0\) 或 \(5\) 结尾的数
- 以其他数结尾的数
因为这两类数内部可以相互转化,但是不能跨类别转化,所以当同时存在两类数就判负。
考虑以 \(0\) 或 \(5\) 结尾的数:显然只有以 \(5\) 结尾的数的改变有意义,而且只可以改变一次,所以就对这些数全部更改一次,然后判断整个序列是否相等就好了。
考虑另一类数:我们发现他们的结尾数字是有规律的 \(2 - 4 - 8 - 6\)。而且我们也会发现一个事实,当两个数仅最后一位不一样时,这两个数无论如何变化都不能相等。
所以这也就启发我们:他们最后变成的相等的数一定是最大值,并且时最大值将最后一位变成 \(2 - 4 - 8 - 6\) 中的某一个。
那么就每个数判一下能不能变成最大值就好了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5+5;
int fx[10] = {2,4,6,8};
int a[MAXN];
signed main(){
int t;
scanf("%lld",&t);
while(t--){
int n;
scanf("%lld",&n);
int mx = 0;
bool flag_1 = false,flag_5 = false;
for(int i=1; i<=n; i++){
scanf("%lld",&a[i]);
int h = a[i] % 10;
if(h == 5 || h == 0) flag_5 = true;
else{
if(h != 2 && h != 4 && h != 8 && h != 6) //把这些数都变成 2 - 4 - 8 - 6 结尾的数
a[i] += h;
flag_1 = true;
}
mx = max(mx,a[i]);
}
if(flag_5 && flag_1){
printf("NO\n");
continue;
}
else if(flag_5){ //只有 0,5 结尾的数,直接暴力改
bool flag = false;
for(int i=1; i<=n; i++){
a[i] += (a[i] % 10);
}
for(int i=2; i<=n; i++){
if(a[i] != a[1]){
flag = true;
break;
}
}
if(!flag) printf("YES\n");
else printf("NO\n");
continue;
}
bool flag = false;
for(int i=1; i<=n; i++){
if(a[i] < mx){ //判断 a[i] 是否可以变成 mx
int pos = (a[i] % 10) / 2 - 1; //将最后一位变成 2
while(pos != 1 && a[i] < mx){
a[i] += (a[i] % 10);
pos = (pos + 1) % 4;
}
if(a[i] > mx){
printf("NO\n");
flag = true;
break;
}
a[i] = a[i] + (mx - a[i])/20 * 20; //变一轮是加 20,所以直接变
pos = (a[i] % 10) / 2 - 1; //让 a[i] 尽可能变大,使得它不超过 mx
while(a[i] < mx){
a[i] += (a[i] % 10);
pos = (pos + 1) % 4;
}
if(a[i] > mx){
printf("NO\n");
flag = true;
break;
}
}
}
if(!flag)
printf("YES\n");
}
return 0;
}
F.Build a Tree and That Is It
题目描述:
题目分析:
赛时的时候可能看见构造一棵树很多人就直接跳了,但是其实一点也不难。
我们考虑题目就给了四个限制:边数、\(1\) 和 \(2\) 和 \(3\) 相互的距离。
那么我们显然最难满足的就是距离的限制,那么我们就考虑如何去搞。虽然题目说无根树,但是我们构造按有根树更好弄。
我们会发现一个性质,如果满足 \(dis_{a,b} + dis_{b,c} = dis_{a,c}\),那么意味着 \(b\) 就是 \(a,c\) 两点形成的链的中间节点。
所以根据这个性质就直接以 \(b\) 为根,向两边延伸,如果距离够了就插入 \(a,c\) 节点,不够就插入没用的节点。这里也就是可以得到判负的一个条件:点的数量不够。
考虑一个如果不存在 \(dis_{a,b} + dis_{b,c} = dis_{a,c}\),那么意味着什么。那么就是 \(a,b,c\) 一定是以另一个点为根的树延伸出去的三条不同链上的点,而且我们也肯定能够解出来他们的距离,也就是解下列这个方程组:
\(dis_i\) 代表从根到 \(i\) 的距离,这样也就是上面的同理,从根延伸,到了距离就把当前点插入然后继续下一条链。这里又是一些的判负条件也就是 \(dis_1,dis_2,dis_3\) 的和、正负的限制。
注意:我们可能存在最后没插完所有节点,那么也好弄,直接全部挂到指定的根上就好了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int dis[4];
void build(int root,int to,int dis,int &now){ //以 now 为根建一条到 to 的边权为 dis 的边
int last = root,now_dis = 1;
while(now_dis <= dis){
if(now_dis == dis){
printf("%d %d\n",last,to);
break;
}
printf("%d %d\n",last,now);
last=now;now++;now_dis++;
}
}
int main(){
int t;
cin>>t;
while(t--){
int n,d12,d23,d13;
cin>>n>>d12>>d23>>d13;
int root = 0;
if(d12 == d23 + d13)
root = 3;
else if(d23 == d12 + d13)
root = 1;
else if(d13 == d12 + d23)
root = 2;
else
root = 4;
if(root == 4 && n == 3){
printf("NO\n");continue;
}
if((root == 1 && d23 > n-1) || (root == 3 && d12 > n-1) || (root == 2 && d13 > n-1)){
printf("NO\n"); continue;
}
if(root == 4){
dis[1] = d13 + d23 + d12 - 2 * d23;
dis[2] = d13 + d23 + d12 - 2 * d13;
dis[3] = d13 + d23 + d12 - 2 * d12;
if(dis[1] % 2!= 0 || dis[2] % 2 != 0 || dis[3] % 2 != 0 ||dis[1] <= 0 || dis[2] <= 0 || dis[3] <= 0){
printf("NO\n");
continue;
}
dis[1]/=2;dis[2]/=2;dis[3]/=2;
if(dis[1] + dis[2] + dis[3] > n-1){
printf("NO\n");
continue;
}
int now = 5; //now 就是指的我们现在使用的没有什么用的节点
printf("YES\n");
build(4,1,dis[1],now);
build(4,2,dis[2],now);
build(4,3,dis[3],now);
while(now <= n) printf("%d %d\n",4,now++);
}
else{
printf("YES\n");
int now = 4;
if(root == 1){
build(1,2,d12,now);
build(1,3,d13,now);
while(now<=n) printf("%d %d\n",1,now++);
}
else if(root == 2){
build(2,1,d12,now);
build(2,3,d23,now);
while(now<=n) printf("%d %d\n",2,now++);
}
else if(root == 3){
build(3,1,d13,now);
build(3,2,d23,now);
while(now<=n) printf("%d %d\n",3,now++);
}
}
}
return 0;
}