Codeforces Round#602(Div 2)解题报告

依旧是赛后补题的一场,争取日后每一场都这样补吧,感谢 $qsc$ 的视频讲解,与他的算法讲堂!

题目链接

A.Math Problem

题意

给你 $n$ 个线段,让你找到一个最短的线段使得该线段与输入的所有线段均有交点

题解

维护一个 $L$,$R$。分别代表找到的线段最左端能到哪里,以及该线段最右端的最小值

  • $L = min(L,r)$
  • $R = max(R,l)$

描述不清,很好理解吧

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5+7;
const int INF = 0x3f3f3f3f;
int main(){
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        int L = INF,R = 0;
        for(int i = 1;i <= n;i++){
            int l,r;
            cin >> l >> r;
            L = min(L,r);
            R = max(R,l);
        }
        if(n == 1)
        	cout << 0 << endl;
        else
        	cout << max(R - L,0) << endl;
    }
    return 0;
}

B.Box

题意

给你一个 $n$ 代表有 $1$ 到 $n$ 个数,给你一个长为 $n$ 的序列, $a[i] = max(a[i]~a[1])$,问能否构造出来一个序列满足题中给的前缀最大值序列(不包括 $0$)。

题解

第一遍没想到,想到了裸的暴力,一直 $T$,我真菜。后来被提醒,可以直接从小到大开始填,如果这个数没填过,就直接填进去,否则从小往大找一个能填的即可。可是我还是一直 $T$,不知道为何,把 $cin,cout,vector$ 都去掉了才过,可能写的是真丑吧

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6+7;
int a[MAXN],vis[MAXN];
int ans[MAXN];
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n,cnt = 0;
        scanf("%d",&n);
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
		int mi = 1;
        bool flag = false;
        for(int i = 1;i <= n;i++){
        	if(!vis[a[i]])
				ans[++cnt] = a[i];
			else {
				if(mi < a[i]){
					ans[++cnt] = mi;
				} else {
					flag  = true;
					break;
				}
			}
			vis[ans[cnt]] = 1;
			while(vis[mi]) mi++;
            
        }
        if(flag)
            printf("-1");
        else
            for(int i = 1;i <= n;i++)
                printf("%d ",ans[i]);
        printf("\n");
        for(int i = 1;i <= n;i++)
            vis[i] = 0;
    }
    return 0;
}

C.Messy

题意

给你一个长为 $n$ (保证偶数)的字符串只包含 $($ 与 $)$,你可以执行 不超过$n$次 的区间翻转即对 $[l...r]$ 翻转,则变成 $[r...l]$,问你能否构造一个序列使得,字符串的所有前缀中只包含 $k$ 个正则表达式的括号。输出翻转的次数与每次翻转的位置。注意只要小于 $n$ 即可,不需要最小。题目保证有解。

正则表达式括号: $()$, $(())$,诸如此类。

题解

他能翻转 $n$ 次意味着,可以得到这个字串的任何形式,至此开始构造。先 构造$k-1$个 $()$ 对,剩余的构造成 $((()))$ 这种就可以了。然后暴力修改即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN = 1e6+7;
vector<P> ans;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(false),cout.tie(false);
    int T;
    cin >> T;
    while(T--){
    	string s,ans_s;
        int n,k;
        cin >> n >> k;
        cin >> s;
        int len = s.size();
        for(int i = 0;i < k-1;i++)
            ans_s += "()";
        int len1 = len - ans_s.size();
        
        for(int i = 0;i < len1/2;i++)
            ans_s += "(";
        for(int i = 0; i < len1/2;i++)
            ans_s += ")";
        for(int i = 0;i < len;i++){
            if(ans_s[i] != s[i]){
                for(int j = i+1;j < len;j++){
                    if(ans_s[i] == s[j]){
                        int x = i,y = j;
                        while(x < y){
                            swap(s[x],s[y]);
                            x++,y--;
                        }
                        ans.push_back(P(i+1,j+1));
                        break;
                    }
                }
            }
        }
        int sz = ans.size();
        cout << sz << '\n';
        for(int i = 0;i < sz;i++){
            cout << ans[i].first << " " << ans[i].second << '\n';
        }
        ans.clear();
    }
    return 0;
}

D.Optimal Subsequences

题意

给定一个长度为 $n$ 的数组,$m$ 次询问,每次询问一个 $k$,一个 $pos$ ,代表在原序列中找到一个长为 $k$ 的子序列,使得这个子序列的和是所有长为 $k$ 的子序列中最大的,如果有和相同的,则选择字典序最小的,然后输出这个子序列的第 $pos$ 位置的值。

题解

D1数据范围100,可以直接贪心暴力。

首先对原序列进行排序,因为要取和最大,可以优先按照值排序,值相同的按照字典序排序。

线段树的解法:
对询问进行离线处理。针对每一个询问有一个规律, $k = i$ 一定是由 $k = i-1$ 得到的,由此我们可以对询问进行排序,按照 $k$ 由小到大进行排序,然后建立权值线段树,维护原序列的坐标,然后二分线段树的边界,找到 $1-K$ 中能包含 $pos$ 个数的最大右边界。
具体看代码把,表达能力不清,难搞

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 7;
const int INF = 0x3f3f3f3f;
int n,m,ans[MAXN];
struct Node{
    int l,r,val;
    int mid(){
        return (l + r) >> 1;
    }
}tree[MAXN << 2];

struct Ans{
    int val,id;
}a[MAXN],b[MAXN];

struct node{
    int k,pos,id;
}q[MAXN];

void PushUp(int rt){
    tree[rt].val = tree[rt << 1].val + tree[rt << 1|1].val;
}

void Build(int l,int r,int rt){
    tree[rt].l = l,tree[rt].r = r;
    if(l == r){
        return ;
    }
    int mid = tree[rt].mid();
    Build(l,mid,rt << 1);
    Build(mid+1,r,rt << 1|1);
    PushUp(rt);
}

void Update(int rt,int pos){
    if(pos == tree[rt].l && pos == tree[rt].r){
        tree[rt].val++;
        return ;
    }
    int mid = tree[rt].mid();
    if(pos <= mid){
        Update(rt << 1,pos);
    } else {
        Update(rt << 1|1,pos);
    }
    PushUp(rt);
}

int Query(int rt,int pos){
    if(pos >= tree[rt].r)
        return tree[rt].val;
    int mid = tree[rt].mid();
    int ret = 0;
    ret += Query(rt << 1,pos);
    if(pos > mid)
        ret += Query(rt << 1|1,pos);
    return ret;
}

bool cmp1(Ans aa,Ans bb){
    if(aa.val != bb.val)
        return aa.val > bb.val;
    return aa.id < bb.id;
}

bool cmp2(node aa,node bb){
    return aa.k < bb.k;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL),cout.tie(NULL);
    cin >> n;
    Build(1,n,1); 
    for(int i = 1;i <= n;i++){
        cin >> a[i].val;
        a[i].id = i;
        b[i] = a[i];
    }
    sort(b+1,b+1+n,cmp1); //贪心排序
    cin >> m;
    for(int i = 1;i <= m;i++){ //对询问进行离线处理
        cin >> q[i].k >> q[i].pos;
        q[i].id = i;
    }
    sort(q+1,q+1+m,cmp2);
    int now = 1;
    for(int i = 1;i <= m;i++){
        for(int j = now;j <= q[i].k;j++)
            Update(1,b[j].id);
        now = q[i].k + 1;
        int l = 1,r = n,tk;
        while(l <= r){ //二分确定最小的包含区间
            int mid = (l + r) >> 1;
            if(Query(1,mid) < q[i].pos)
                l = mid+1;
            else {
                r = mid - 1;
                tk = mid;
            }
        }
        ans[q[i].id] = a[tk].val;
    }
    for(int i = 1;i <= m;i++)
        cout << ans[i] << endl;
    return 0;
}

主席树解法:
貌似和权值线段树一样,维护的是区间下标第 $k$ 小,加上贪心的排序即可

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
vector<int>v;
struct node {
    int l,r,s;
} t[N*40];
int rt[N],cnt;
void update(int l,int r,int x,int &y,int pos) {
    y=++cnt;
    t[y]=t[x];
    t[y].s++;
    if(l==r)
        return;
    int m=(l+r)>>1;
    if(pos<=m)
        update(l,m,t[x].l,t[y].l,pos);
    else
        update(m+1,r,t[x].r,t[y].r,pos);
}
int query(int l,int r,int x,int y,int num) {
    if(l==r)
        return l;
    int sum=t[t[y].l].s-t[t[x].l].s;
    int m=(l+r)>>1;
    if(sum>=num)
        return query(l,m,t[x].l,t[y].l,num);
    else
        return query(m+1,r,t[x].r,t[y].r,num-sum);
}
struct Node{
    int x,id;
}a[N],b[N];
map<int,int> M;
bool cmp1(Node aa,Node bb){
    if(aa.x != bb.x)
        return aa.x > bb.x;
    return aa.id < bb.id;
}

int main() {
    int n,m;
    int x,l,r;
    scanf("%d",&n);
    for(int i=1; i<=n; ++i){
        scanf("%d",&a[i].x);
        a[i].id = i;
        b[i] = a[i];
        M[i] = a[i].x;
    }
    sort(b+1,b+1+n,cmp1);
    for(int i=1; i<=n; ++i)
        update(1,n,rt[i-1],rt[i],b[i].id);
    cin >> m;
    for(int i=1; i<=m; ++i) {
        int k,pos;
        cin >> k >> pos;
        int tt = query(1,n,rt[0],rt[k],pos);
        printf("%d\n",M[tt]);
    }
    return 0;
}

E.Arson In Berland Forest

待补

F.Wrong Answer on test 233

题意

给定一个由 $n$ 道题的试卷,每个题由 $k$ 个选项,接着给你 $n$ 个数代表,每道题的正确答案。判题机器有些问题,会把你选择的答案都往前进 $1$ 个位置 如果是第 $n$ 个题则到 第$1$道题。问你有多少种组合方式使得,经过机器错误读入后比错误读入前能多得分的序列个数。

题解

面向题解解题

设 $dp[i][j]$ 代表第 $i$ 个题在交换后得到 $j$ 分的可能。
如果 $h[i] == h[i+1]$ 可以得出,这个题经过交换后,他的分值不变,因为由 $K$ 个选项,任意选则,所以状态转移方程为

dp[i][j] = dp[i-1][j] * k;

如果 $h[i] != h[i+1]$ 分为三种情况

  1. 改前对,改后错
  2. 改前错,改后对
  3. 同对或同错,分值不变

对于前两种情况,只有固定的一种选项,对于最后一种则由 $(k-2)$ 种选择情况。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 4005;
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
ll n,k,h[MAXN];
ll dp[2005][4005];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(NULL),cout.tie(NULL);
    cin >> n >> k;
    for(int i = 1;i <= n;i++)
        cin >> h[i];
    dp[0][2001] = 1;
    for(int i = 1;i <= n;i++){
       for(int j = 1;j <= 4001;j++){
            if(h[i] == h[i%n+1])
                dp[i][j] = dp[i-1][j] * k % mod;
            else
                dp[i][j] = (dp[i-1][j-1] + dp[i-1][j+1] + dp[i-1][j]* (k-2)) % mod;
       		//printf("%d %d %lld\n",i,j,dp[i][j]);
	   }
    }
    ll ans = 0;
    for(int i = 1;i <= n;i++)
        ans = (ans + dp[n][2001+i]) % mod;
    cout << ans << endl;
    return 0;
}

F2 脑子不够了!!!

有问题欢迎联系:QQ:2112370160 !