CF1214

C题WA3发的菜鸡还能涨分

A

发现货币面值都是倍数关系,直接暴力枚举第第一种换了多少个更新答案就好了

B

按照题意模拟

C

首先,左括号的数量不等于有括号的数量一定无解

想等的话在括号匹配的过程有在某一时刻栈中存在两个或者以上的右括号便无解

错误原因:思路出了点小问题

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 5e5 + 3;
char s[N];
int sta[N];
int n;
inline int read(){
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar();
    }
    while(isdigit(ch)){
        v = v * 10 + ch - 48;
        ch = getchar();
    }
    return v * c;
}
int sum1;
int tot;
int main(){
    n = read();
    scanf("%s",s + 1);
    for(int i = 1;i <= n;++i)
        sum1 += (s[i] == ')');  
    if(sum1 != n - sum1){printf("No\n");return 0;}
    for(int i = 1;i <= n;++i){
        if(s[i] == '(') tot++;  
        else{
            tot--;
            if(tot <= -2){printf("No\n");return 0;}
        }
    }
    if(tot >= 2) printf("No\n");
    else printf("Yes\n");
    return 0;
}

D

我也不知道自己写了个什么算法,但是它过掉了

考虑这种情况

如图,如果黄色障碍点的话

绿色的点不管是不是障碍,都可以当做障碍去看

之后我们扩展一下

\(dis_{x,y}\)表示把经过\((x,y)\)发出的路径全部搞死的最小代价

如果\((x,y)\)是空地并且\((x,y)\)\((1,1)\)联通

\(dis_{x,y} = (dis_{x + 1,y} > 0 ) + (dis_{x,y + 1} > 0)\)

否则\(dis_{x,y} = 0\)

我们需要倒着这么转移

当然边界情况需要判断一下

之后我们就可以枚举所有的对角线的dis值之和

取个\(min\)

但是,答案如果涨这个样子怎么办

很明显我们只需要堵住一个格子

但是很明显答案不是对角线

想想我们\(dp\)的过程,我们已经把下面的贡献转移到了上面

所以他的dis数组是这样的,
\[ \begin{array}{|c|c|c|c|}\hline 1 & {2} & {1} & {0} \\ \hline 1 & {1} & {1} & {0} \\ \hline 0 & {0} & {1} & {0} \\ \hline 0 & {0} & {1} & {1} \\ \hline\end{array} \]
然后这样发现答案也是1

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 3;
string s[N];
bool book[N << 1];
int n,m;
int dis[N << 1];
int dx[2] = {0,1},dy[2] = {1,0};
inline int read(){
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar();
    }
    while(isdigit(ch)){
        v = v * 10 + ch - 48;
        ch = getchar();
    }
    return v * c;
}
inline int bfs(){
    memset(book,0,sizeof(book));
    book[0] = 1;
    queue <int> q;q.push(0);
    while(!q.empty()){
        int k = q.front();q.pop();
        int x = k / m,y = k % m;
        for(int i = 0;i < 2;++i){
            int xx = x + dx[i],yy = y + dy[i];
    //      if(book[]) continue;
            if(xx < 0 || xx >= n || yy < 0 || yy >= m || book[xx * m + yy] || s[xx][yy] == '#') continue;
            int z = xx * m + yy;
            q.push(xx * m + yy);
            book[xx * m + yy] = 1;
        }
    }
}
int sum[N << 1];
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 0;i < n;++i) cin >> s[i];
    memset(dis,0x3f,sizeof(dis));
    for(int i = 0;i < n;++i){
        for(int j = 0;j < m;++j)
        if(s[i][j] == '#') dis[i * m + j] = 0;
    }
    bfs();
    dis[n * m - 1] = 1;
    for(int i = n - 1;i >= 0;--i){
        for(int j = m - 1;j >= 0;--j){
            int x = i * m + j;
            if(s[i][j] == '#') continue; 
            if(!book[i * m + j]){dis[x] = 0;continue;}
            if(i == n - 1 && j == m - 1) continue;
            if(i == n - 1) dis[x] = (dis[x + 1] > 0);
            else if(j == m - 1) dis[x] = (dis[x + m] > 0);
            else dis[x] = (dis[x + 1] > 0) + (dis[x + m] > 0);
        }
    }
    int ans = 2;
    for(int i = 0;i < n;++i){
        for(int j = 0;j < m;++j){
            sum[i + j] += (dis[i * m + j] > 0);
        }
    }
    for(int i = 1;i <= n + m - 2 - 1;++i) ans = min(ans,sum[i]);
//  ans = min(ans,sum[1]);
    printf("%d\n",ans); 
    return 0;
}

update:赛后被刁神吊打了Orz

这道题完全不需要这么麻烦

因为搜索出所有可能到达的点之后,维护两个指针

一个能向下走就向下走,另外一个能向右走就向右走

如果这两个路径有交点,那么一定是所有路径的必经点

直接把这个点删掉就好了QAQ

E

构造题

我们先把所有距离从大到小排序

因为题目保证有解

把所有的点对按照\(d\)排序之后,我们发现一个神奇的性质

对于排序完之后的数组的某一下标\(i\)

\(i + 1\)的距离在链上对应的点最大在\(i\)对应的点的右边

可能有些不好理解,结合图片理解一下

我们先构造一条这样的链

因为有距离\(d\)排完序之后不增的限制,所以\(i + 1\)对应为位置最多如图红色所示

这样我们暴力填上面的,对应的点就向这个样子挂下去

但是这样可能有个问题

如果上面的链的长度大于了\(n\)怎么办?

不用担心

每一个空点前面的点一定有儿子

因为有距离\(d\)的限制

所以我只直接把前面节点的儿子放到这个空点上就好了

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 3;
vector <int> G[N];
int ans[N];
int sum[N];
int tot; 
int n;
struct node{
    int id;
    int val;    
}a[N];
inline int read(){
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar();
    }
    while(isdigit(ch)){
        v = v * 10 + ch - 48;
        ch = getchar();
    }
    return v * c;
}
inline bool cmp(node x,node y){
    return x.val > y.val;   
}
int main(){
    n = read();
    for(int i = 1;i <= n;++i){
        a[i].val = read();
        a[i].id = i;
    }
    sort(a + 1,a + n + 1,cmp);
    int len = 0;
    for(int i = 1;i <= n;++i){
        G[i + a[i].val - 1].push_back((a[i].id << 1) - 1);
        sum[i] = a[i].id << 1;
        len = max(len,i + a[i].val - 1);
    }
    for(int i = 1;i <= len;++i){
        if(!sum[i]){
            int x = G[i - 1].size();
            sum[i] = G[i - 1][x - 1];
            G[i - 1].pop_back();
        }
    }
    for(int i = 1;i <= len;++i){
        if(i != len) printf("%d %d\n",sum[i],sum[i + 1]);
        for(int j = 0;j < (int)G[i].size();++j){
            printf("%d %d\n",sum[i],G[i][j]);   
        }
    }
    return 0;
}

F

咕咕咕

经过\(smy\)\(wqy\)的讲解之后,终于来补题了 QAQ

首先,我们需要证明,最佳答案的匹配一应不会覆盖整个环

考虑如果没有被覆盖的点,说明可能出现了下面的情况

红色的是同一类,黄色的同一类,最左右还有一个环没有画出来

你发现

把所有匹配的位置对应叮会更优(还有个环没画出来)

然后我们就可以枚举断点的位置

让所有的点都在这个位置一侧去匹配

我们想

枚举断点后断点左边的坐标要整体\(+m\)

对于一个人\(x_i\)和办公室\(y_i\)

他们匹配的代价是\(|x_i - y_i|\),

发现\(x_i\),和\(y_i\)的系数为\(1,-1\)

我们要求的实际上是
\[ \sum_{i = 1}^n a _i x_i +b_iy_i \]
\(a_i,b_i\)是他们贡献的系数

贡献的系数发现只取决于他是和左边的点匹配还是和右边的点去匹配

我们设\(d_i\)来表示这个东西

点上面的数就是他们\(d\)的值,然后我们发现,\(d >=0\)的坐标对答案的贡献都是\(-1\)

\(d < 0\)的坐标对答案啊的贡献都是\(1\)

这样的话,我们发现每一次枚举断点,消掉部分的会整体-1,对立部分整体+1

这个东西我们直接用指针去维护

维护到现在应该哪一部分的贡献将会改变

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 3;
const LL INF = 1e15;
struct node{
    LL x;
    int id;
    int type;
}a[N];
int d[N];
LL _suma[N],_sumb[N];
LL *suma = _suma + 400000,*sumb = _sumb + 400000;
int n,m;
queue <int> q1,q2;
int as[N];
LL ans = INF,res;
inline int read(){
    int v = 0,c = 1;char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') c = -1;
        ch = getchar();
    }
    while(isdigit(ch)){
        v = v * 10 + ch - 48;
        ch = getchar();
    }
    return v * c;
}
inline bool cmp(node xx,node yy){
    return xx.x < yy.x; 
}
int main(){
    m = read(),n = read();
    for(int i = 1;i <= n;++i){
        a[i].x = read();
        a[i].id = i;
        a[i].type = 1;
    }
    for(int i = 1;i <= n;++i){
        a[i + n].x = read();
        a[i + n].id = i;
        a[i + n].type = 2;  
    }
    sort(a + 1,a + 2 * n + 1,cmp);
    int na = 0,nb = 0;
    for(int i = 1;i <= 2 * n;++i){
        if(a[i].type == 1){
            d[i] = na - nb;
            suma[na - nb] += a[i].x;
            na++;
        }
        else{
            d[i] = nb - na;
            sumb[nb - na] += a[i].x;
            nb++;   
        }
    }
    int pos = 0;
    for(int i = -n;i <= n;++i){
        if(i >= 0) res -= suma[i] + sumb[i];
        else res += suma[i] + sumb[i];
//      printf("i:%d suma:%lld sumb:%lld\n",i,suma[i],sumb[i]);
    }
//  for(int i = 1;i <= 2 * n;++i) printf("%d\n",d[i]);
//  printf("%d\n",res);
    ans = res;
    int ta = 0,tb = 0;
    for(int i = 1;i <= 2 * n;++i){
    //  printf("%d %d %d\n",i,ta,tb);
        if(a[i].type == 1){
            res += suma[ta];
            suma[d[i]] += m;
            res += suma[ta];
            ta++,tb--;
            res -= sumb[tb] * 2;
        }
        else{
            res += sumb[tb];
            sumb[d[i]] += m;
            res += sumb[tb];
            ta--,tb++;
            res -= suma[ta] * 2;    
        }
        if(res < ans){
            pos = i;
            ans = res;
        }
    }
    printf("%lld\n",ans);
    for(int i = pos + 1;i <= 2 * n;++i){
        if(a[i].type == 1){
            if(!q2.empty()) as[a[i].id] = q2.front(),q2.pop();
            else q1.push(a[i].id);
        }
        else{
            if(!q1.empty()) as[q1.front()] = a[i].id,q1.pop();
            else q2.push(a[i].id);  
        }
    }
    for(int i = 1;i <= pos;++i){
        if(a[i].type == 1){
            if(!q2.empty()) as[a[i].id] = q2.front(),q2.pop();
            else q1.push(a[i].id);
        }
        else{
            if(!q1.empty()) as[q1.front()] = a[i].id,q1.pop();
            else q2.push(a[i].id);  
        }
    }
    for(int i = 1;i <= n;++i) printf("%d ",as[i]);
    return 0;
}