18.10.7 考试总结

 

这道题还是很温柔的签到题 看了两分钟就搞出来了 然后忘记在每次更新队首之后重新更新答案了

然后幸好改出来惹 

用滑动窗口维护对于每个右端点的最优左端点 就是每次加入元素 如果合法那么我就贪心的弹出队头然后区间长度取最小值即可

代码

#include <bits/stdc++.h>
#define oo 1e9
using namespace std;

const int N = 2 * 1e5 + 5;
int T, a[N], n, k, r, b, q, cmd[N], num, len;
int t[N];

void clear( ) {
    memset(t, 0, sizeof(t));
    memset(cmd, 0, sizeof(cmd));
    num = 0;
}

bool check(int pos) {
    
    if(t[a[pos]] - 1 >= cmd[a[pos]]) return true;
    return false;
}

void deal( ) {
    
    int tot = 0; int head = 0, tail = 0, las = 1;
    len = oo;
    for(int i = 1;i <= n;i ++) {
        tail ++; t[a[i]] ++;
        if(t[a[i]] == cmd[a[i]]) tot ++;
        while(check(las) && head < tail) {
            t[a[las]] -- ;
            las ++, head ++;
        }
        if(tot == num) len = min(len, i - las + 1);
    }
}

int read( ) {
    
    int t = 1, ans = 0;
    char x; x = getchar( );
    while(x < '0' || x > '9') {
        if(x == '-') t = -1;
        x = getchar( );
    }
    while(x >= '0' && x <= '9') {
        ans = ans * 10 + x - '0';
        x = getchar( );
    }
    return ans * t;
}

void Solve( ) {
    
    scanf("%d",& T);
    while(T --) {
        clear( );
        n = read( ), k = read( ), r = read( );
        for(int i = 1;i <= n;i ++) a[i] = read( );
        for(int i = 1;i <= r;i ++) {
            b = read( ); q = read( );
            cmd[b] = max(cmd[b], q);
        }
        for(int i = 0;i <= k;i ++) 
            if(cmd[i] > 0) num ++;
        deal( );
        if(len == oo) printf("DESTROY ALL\n");
        else printf("%d\n", len);
    }
}

int main( ) {
    
    freopen("drop.in","r",stdin);
    freopen("drop.out","w",stdout);
    Solve( );
}

   

emmmmm考试的时候wans给我说这道题是2sat我还不信... 然后就被华丽丽的打脸饿

先复习一下这玩意儿吧 这个玩意儿是对于很多约束条件进行建边 $a -> b$表示选择了$a$就必须选择$b$

同一种东西有两种状态$a, -a$ 显然这两种状态是不能同时出现的 也就是说如果我沿着一种状态可以走到另一状态 那么这个条件就是不合法的

并且普通的$2sat$具有对称性 比如某个条件$(i, j)$表示$i, j$ 不能共存 也就是说连边$i -> -j,j -> -i$

所以对于2sat而言能从$i$走到$j$就一定能从$j$走到$i$ 也就是说他们在同一个强连通分量中 跑一遍$tarjan$ 在判断每个点的两个状态是否在同一个强连通分量就好了

那么对于这道题而言 题目条件$(i, j)$建边方式和上面的一样 所以也就是说这样子建图是尽可能保证不出现行星被炸掉的情况

所以我们的目的就转化成了添加若干条边 使某物品的两状态可以互相到达 由于对称性 我们只需要保证一个点能走到另一个点就可以了

假设上面的点表示正状态 下面负状态 那么我们建边只能从上面的点指向下面的点

对于这种上面的状态可以指向下面的状态而言 因为对称性 所以只要下面的点能够到上面 根据对称性 就只需要添加一条虚线边即可 直接到达自己显然不需要添加任何边

那么相反的 就只需要多添加自己的上面指向下面那条边就可以了 

那么无解的情况就是下面的点没有一个能够到上面 也就是说走不通路的情况了 因为我们只能从上往下建边

枚举每个点 在这样子讨论一下跑跑dfs就可以了

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 4000 + 5;
int T, n, m, tot, head[N], nex[2 * N],tov[2 * N];
bool vis[N];

void add(int u, int v) {
    
    tot ++;
    nex[tot] = head[u];
    tov[tot] = v;
    head[u] = tot;
}

int read( ) {
    
    int t = 1, ans = 0;
    char x; x = getchar( );
    while(x < '0' || x > '9') {
        if(x == '-') t = -1;
        x = getchar( );
    }
    while(x >= '0' && x <= '9') {
        ans = ans * 10 + x - '0';
        x = getchar( );
    }
    return t * ans;
}

void Add_Edge( ) {
    
    n = read( ),m = read( );
    for(int i = 1;i <= m;i ++) {
        int x, y;
        x = read( ), y = read( );
        int xx = x > 0 ? x + n : -x;
        int yy = y > 0 ? y + n : -y;
        if(x < 0) x = -x + n;
        if(y < 0) y = -y + n;
        add(x, yy); add(y, xx);
    }
}

void clear( ) {
    
    memset(head, 0, sizeof(head));
    tot = 0;
}

void dfs(int u) {
    
    vis[u] = true;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(! vis[v]) dfs(v);
    }
}

void Solve( ) {
    
    scanf("%d",& T);
    while(T --) {
        clear( );
        Add_Edge( );
        int ans = 3;
        for(int i = 1;i <= n;i ++) {
            memset(vis, 0, sizeof(vis));
            dfs(i);
            int ret = 3;
            if(vis[i + n]) {
                memset(vis, 0, sizeof(vis));
                dfs(i + n); if(vis[i]) {ans = 0; continue;}
                for(int j = 1;j <= n;j ++) if(vis[j]) {
                    ret = 1; break;
                }
            }
            else {
                memset(vis, 0, sizeof(vis));
                dfs(i + n); if(vis[i]) {ans = min(ans, 1); continue;}
                for(int j = 1;j <= n;j ++) if(vis[j]) {
                    ret = 2; break;
                }
            }
            ans = min(ans, ret);    
        }
        if(ans == 3) printf("No Way\n");
        else printf("%d\n",ans);
    }
}

int main( ) {

    freopen("god.in","r",stdin);
    freopen("god.out","w",stdout);
    Solve( );
}

 

  

 

 

考试的时候$n$方暴力真的美滋滋还有60分

正解的思路其实和暴力是一样的 但是他用了无比优美的优化

暴力的思想大概是 贪心

 

先从左往右扫,如果某一时刻不满足要求,则需要删除前面中某一个支持对方的人。我们贪心地选择删除当前时刻访问的人(肯定是支持对方),然后继续往后扫。

然后再从右往左扫,作相同的操作。

直观地理解是这样的:我们尽量删除靠右的人,使得从右往左扫时少删除一些人。

可以采用交换论证法证明这贪心是对的。

 

我们可以把$C$看作$-1$,$V$看作$1$ 那么对于一段从右向左的区间而言 最小后缀和相反数就能表示杀掉人的个数 因为$C$越多 后缀和越小

题解写得好 我复制一波 在按照自己的理解修改一下

可以发现一个区间从左往右扫完,从右往左扫的这个过程可以不用实现出来。只需要求出剩下节点中从右端点开始的最小后缀和的相反数即可。

然后我们发现,如果两个询问区间拥有相同的左端点,则只需要作一次从左往右扫的工作。这使我们想到要离线化解决问题。

我们将询问按左端点排序,按照左端点从大到小的顺序求解询问。 至于每个区间的最小后缀和很容系想到用线段树维护

如果已知从$i$开始向右扫需要删除那些结点,则从$i - 1$开始向右扫需要删除那些结点可以快速求出 在进行修改的同时进行线段树的维护。

具体来说 如果$i-1$是支持者 则左数第一个被删除的结点与它抵消 相当于把他放了出来 也就是说他会在后续的后缀和中做出$-1$的贡献 注意后缀和指的是剩下的人和

如果$i - 1$是反对者 则加入被删除的结点里 他对线段树不作出贡献。

该过程可以用栈维护。

那么对于每个询问 可以在栈中二分得到从左往右的要杀的人的个数 在线段树中求最小后缀和 取相反数相加即可

总的来说 栈维护从左往右杀的人 线段树维护剩下的人从右往左杀的人

代码(这道题我竟然没看std改出来了hhh)

#include <bits/stdc++.h>
using namespace std;

const int N = 5 * 1e5 + 5;
int a[N], q, sum[4 * N], f[4 * N], stk[N], top, n, ans[N];
char s[N];
struct event {
    
    int l, r, id;
    event(int l = 0, int r = 0, int id = 0): l(l), r(r), id(id) {}
}b[N];

bool cmp(const event & a, const event & b) {
    return a.l < b.l;
}

void Init( ) {
    
    scanf("%d",& n);
    scanf("%s",s + 1);
    for(int i = 1;i <= n;i ++) a[i] = s[i] == 'C' ? 1 : -1;
    scanf("%d",& q);
    for(int i = 1;i <= q;i ++) {
        int l,r;
        scanf("%d%d",& l,& r);
        b[i] = event(l, r, i);
    }
    sort(b + 1, b + q + 1, cmp);
}

void update(int o) {
    
    sum[o] = sum[2 * o] + sum[2 * o + 1];
    f[o] = min(f[2 * o + 1], sum[2 * o + 1] + f[2 * o]);
}

void modify(int o, int l, int r, int pos, int del) {
    
    if(l == r) {
        f[o] += del;
        sum[o] += del;
        return ;
    }
    int mid = l + r >> 1;
    if(pos <= mid) modify(2 * o, l, mid, pos, del);
    else modify(2 * o + 1, mid + 1, r, pos, del);
    update(o);
}

void init(int pos) {
    
    if(a[pos] == 1) {
        stk[++ top] = pos;
        //modify(1, 1, n, pos, -1);
    }
    else {
        modify(1, 1, n, pos, 1);
        if(top > 0) {
            modify(1, 1, n, stk[top --], -1);
        }
    }
}

bool check(int pos, int side) {
    
    return stk[pos] <= side;
}

int find_pos(int pos) {
    
    int l = 1,r = top,ans = top + 1;
    bool tag = false;
    while(l <= r) {
        int mid = l + r >> 1;
        if(check(mid, pos)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    return ans;
}

event query(int o, int l, int r, int L, int R) {
    
    if(l >= L && r <= R) {
        return event(f[o], sum[o], 0);
    }
    int mid = l + r >> 1;
    if(R <= mid) return query(2 * o, l, mid, L, R);
    if(L > mid)  return query(2 * o + 1, mid + 1, r, L, R);
    event q1 = query(2 * o, l, mid, L, R);
    event q2 = query(2 * o + 1, mid + 1, r, L, R);
    event q3; 
    q3.l = min(q1.l + q2.r, q2.l); q3.r = q2.r + q1.r;
    return q3;
}

void Solve( ) {
    
    int now = q;
    for(int i = n;i >= 1;i --) {
        init(i);event aa = query(1, 1, n, 1, 11);
        while(i == b[now].l) {
            
            int num1 = find_pos(b[now].r); 
            num1 = top - num1 + 1;
            event num2 = query(1, 1, n, b[now].l, b[now].r); 
            int as = num1 + max(0, -num2.l);
            ans[b[now].id] = as; now --;
        }
    }
    for(int i = 1;i <= q;i ++) printf("%d\n",ans[i]);
}

int main( ) {
    
    freopen("sworder.in","r",stdin);
    freopen("sworder.out","w",stdout);
    Init( );
    Solve( );
    return 0;
}
posted @ 2018-10-07 19:38  阿澈说他也想好好学习  阅读(211)  评论(0编辑  收藏  举报