【10.7校内测试】【队列滑窗】【2-sat】【贪心+栈二分+线段树(noip模拟好题)】【生日祭!】

比较好想的一道题,直接用队列滑窗,因为扫一遍往队列里加东西时,改变的只有一个值,开桶储存好就行了!

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

int n, k, r;

inline int min(int a, int b) {
    return a > b ? b : a;
}

inline int max(int a, int b) {
    return a > b ? a : b;
}

int sum[200005], q[200005], cnt[200005], vis[200005], ned[200005];
int a[200005];

int main() {
    freopen("drop.in", "r", stdin);
    freopen("drop.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T --) {
        memset(sum, 0, sizeof(sum));
        memset(q, 0, sizeof(q));
        memset(cnt, 0, sizeof(cnt));
        memset(vis, 0, sizeof(vis));
        memset(ned, 0, sizeof(ned));
        scanf("%d%d%d", &n, &k, &r);
        int tot = 0, fl = 0;
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &a[i]);
            sum[a[i]] ++;
        }
        for(int i = 1; i <= r; i ++) {
            int b, p;
            scanf("%d%d", &b, &p);
            if(!vis[b])    vis[b] = 1, tot ++;
            if(sum[b] < p) {
                fl = 1; break;
            }
            ned[b] = max(ned[b], p);
        }
        if(fl) {
            printf("DESTROY ALL\n"); continue;
        }
        int ans = 0x3f3f3f3f;
        int h = 0, t = 0, now = 0;
        for(int i = 1; i <= n; i ++) {
            if(vis[a[i]]) {
                q[++t] = i; cnt[a[i]] ++; if(cnt[a[i]] == ned[a[i]]) now ++;
            }
            while(now == tot && h < t) {
                ans = min(ans, q[t] - q[h+1] + 1);
                cnt[a[q[h+1]]] --;
                if(cnt[a[q[h+1]]] < ned[a[q[h+1]]])    now --;
                h ++;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
} 

考场上想到$2-sat$但是忘得差不多了,打死都理不清楚关系。

这道题算是$2-sat$板子题了,主要是如何判断的思想。

首先题目条件疯狂暗示,但是和$2-sat$的一般理解方式不同。题目上给的约束条件我们按$2-sat$让他们避免相连,实际上就是题目中的防守文明,我们要摧毁文明,实际上是要让防守文明失效。即至少有一个点和它的负点相互相通。因为$n$很小就可以用两次$dfs$实现,然后就是分类讨论来判断该加几条边。

1、假如初始连边中就有某一个点对正负点相互连接,答案就是0.

2、假如一个点对中正点连向负点:

   1)如果负点可以连向另一点对的正点,根据$2-sat$的对称行,另一点对的负点一定可以连向当前点对的正点,所以我们只用加条件$(i,j)$,就是在$-i$和$-j$之间连了一条边,使i点对相互连通。

   2)如果负点没有连向任意点对的正点,那么一定无解,输出$No Way$。

3、假如一个点对中负点连向正点:

那么只用加一个条件$(i,i)$,相当于在$i$和$-i$间连边即可。

4、假如点对之间没有连边:

1)将2、3结合,但是必须满足2的条件,这样加两条边即可。

2)没有2的条件,就无法加边,输出$No Way$。

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

struct Node {
    int u, v, nex;
    Node(int u = 0, int v = 0, int nex = 0) :
        u(u), v(v), nex(nex) { }
} Edge[4005];

int h[4004], stot;
void add(int u, int v) {
    Edge[++stot] = Node(u, v, h[u]);
    h[u] = stot;
}

int vis[4005];
void dfs(int u) {
    vis[u] = 1;
    for(int i = h[u]; i; i = Edge[i].nex) {
        int v = Edge[i].v;
        if(vis[v])    continue;
        dfs(v);
    }
}

int n, m;
int main() {
    freopen("god.in", "r", stdin);
    freopen("god.out", "w", stdout);
    int T;
    scanf("%d", &T);
    while(T --) {
        memset(h, 0, sizeof(h));
        stot = 0;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m ; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            if(x > 0 || y < 0) swap(x, y);
            if(x > 0 && y > 0) {
                add(x, y + n);
                add(y, x + n);
            }
            if(x < 0 && y < 0) {
                add(-x + n, -y);
                add(-y + n, -x);
            }
            if(x < 0 && y > 0) {
                add(-x + n, y + n);
                add(y, -x);
            }
        }
        int tag1 = 0, tag2 = 0, tag0 = 0;
        for(int i = 1; i <= n; i ++) {
            memset(vis, 0, sizeof(vis));
            dfs(i);
            int fl = 0;
            if(vis[i + n]) {
                memset(vis, 0, sizeof(vis));
                dfs(i + n);
                fl = 1;
                if(vis[i]) {
                    tag0 = 1; break;
                }
                for(int j = 1; j <= n; j ++) {
                    if(j != i && vis[j]) {
                        tag1 = 1; break;
                    }
                }
            }
            memset(vis, 0, sizeof(vis));
            dfs(i + n);
            if(vis[i]) {
                tag1 = 1;
            } else if(!fl) {
                for(int j = 1; j <= n; j ++) {
                    if(j != i && vis[j]) {
                        tag2 = 1; break;
                    }
                }
            }
        }
        if(tag0)        printf("0\n");
        else if(tag1)    printf("1\n");
        else if(tag2)    printf("2\n");
        else            printf("No Way\n");
    }

    return 0;
}

一道好题。(但是现在不想写题解了aaaa)

所以复制题解~

60分:

考虑这样一个贪心:
先从左往右扫,如果某一时刻不满足要求,则需要删除前面中某一个支持对方的人。我们贪心地选择删除当前时刻访问的人(肯定是支持对方),然后继续往后扫。
然后再从右往左扫,作相同的操作。
直观地理解是这样的:我们尽量删除靠右的人,使得从右往左扫时少删除一些人。
可以采用交换论证法证明这贪心是对的。

80-100:

首先我们可以发现从左往右扫完,从右往左扫的这个过程可以不用实现出来。只需要求出右端点开始的最小后缀和的相反数即可。
然后我们发现,如果两个询问区间拥有相同的左端点,则只需要作一次从左往右扫的工作。这使我们想到要离线化解决问题。

我们将询问按左端点排序,按照左端点从大到小的顺序求解询问。
如果已知从 i 开始向右扫需要删除那些结点,则从 i-1 开始向右扫需要删除那些结点可以快速求出。具体来说,如果i-1是支持者,则左数第一个被删除的结点与它抵消;如果i-1是反对者,则加入被删除的结点里。
该过程可以用栈维护。

通过在栈里面二分,我们可以知道区间[l, r]在从左往右扫时需要删除的结点数量。
现在问题就是求解以 r 为端点的最小后缀和。
这个东西可以用块状数组O(sqrt(N))维护(这就是80%算法的由来),更好的方法应该是用线段树O(log(N))维护
于是该题就在O((N+Q)logN)的时间复杂度内解决了。

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

struct Tree {
    int mi, sum;
} TR[500005*4];

int n, m;
char s[500005];

void update(int nd) {
    TR[nd].mi = min(TR[nd << 1 | 1].mi, TR[nd << 1].mi + TR[nd << 1 | 1].sum);
    TR[nd].sum = TR[nd << 1].sum + TR[nd << 1 | 1].sum;
}

void build(int nd, int l, int r) {
    if(l == r) {
        TR[nd].mi = TR[nd].sum = 0;
        return ;
    }
    int mid = (l + r) >> 1;
    build(nd << 1, l, mid);
    build(nd << 1, mid + 1, r);
    update(nd);
}

void modify(int nd, int l, int r, int pos, int d) {
    if(l == r) {
        TR[nd].sum = d;
        TR[nd].mi = d;
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid)    modify(nd << 1, l, mid, pos, d);
    else            modify(nd << 1 | 1, mid + 1, r, pos, d);
    update(nd);
}

Tree query(int nd, int l, int r, int pos) {
    if(l == r) return TR[nd];
    int mid = (l + r) >> 1;
    if(pos <= mid)    return query(nd << 1, l, mid, pos);
    else {
        Tree res = query(nd << 1 | 1, mid + 1, r, pos);
        res.mi = min(res.mi, res.sum + TR[nd << 1].mi);
        res.sum = res.sum + TR[nd << 1].sum;
        return res;
    }
}

vector < pair < int , int > > qus[500005];
int tp = 0, stk[500005], ans[500005];

int find(int pos) {
    int l = 1, r = tp, an = tp + 1;
    while(l <= r) {
        int mid = (l + r) >> 1;
        if(stk[mid] > pos)    l = mid + 1;
        else an = mid, r = mid - 1;
    }
    return an;
}

int main() {
    freopen("sworder.in", "r", stdin);
    freopen("sworder.out", "w", stdout);
    scanf("%d", &n);
    scanf("%s", s + 1);
    scanf("%d", &m);
    for(int i = 1; i <= m; i ++) {
        int l, r;
        scanf("%d%d", &l, &r);
        qus[l].push_back(make_pair(r, i));
    }
    build(1, 1, n);
    for(int i = n; i >= 1; i --) {
        if(s[i] == ‘C‘)    stk[++tp] = i;
        else {
            if(tp) {
                modify(1, 1, n, stk[tp], -1);
                stk[tp--] = 0;
            }
            modify(1, 1, n, i, 1);
        }
        for(int j = 0; j < qus[i].size(); j ++) {
            int pos = qus[i][j].first;
            int tmp = find(pos);
            ans[qus[i][j].second] = (tp - tmp + 1) - min(0, query(1, 1, n, pos).mi);
        }
    }
    for(int i = 1; i <= m; i ++)    printf("%d\n", ans[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/wans-caesar-02111007/p/9751152.html

时间: 2024-10-30 12:06:17

【10.7校内测试】【队列滑窗】【2-sat】【贪心+栈二分+线段树(noip模拟好题)】【生日祭!】的相关文章

【10.5校内测试】【DP】【概率】

转移都很明显的一道DP题.按照不优化的思路,定义状态$dp[i][j][0/1]$表示吃到第$i$天,当前胃容量为$j$,前一天吃(1)或不吃(0)时能够得到的最大价值. 因为有一个两天不吃可以复原容量的定义,所以需要前一天的状态. 而注意,容量表示的是当前第$i$天吃之前的容量. 然后考虑压缩空间,将天数滚动.要注意的是滚动过后$now$指向的是$i$后一天的状态,因此刷表更新. #include<bits/stdc++.h> using namespace std; int n, m; i

【10.17校内测试】【二维数位DP】【博弈论/预处理】【玄学(?)DP】

Solution 几乎是秒想到的水题叻! 异或很容易想到每一位单独做贡献,所以我们需要统计的是区间内每一位上做的贡献,就是统计区间内每一位是1的数的数量. 所以就写数位dp辣!(昨天才做了数字统计不要太作弊啊!) Code #include<bits/stdc++.h> #define LL long long #define mod 1000000007 using namespace std; inline void read(LL &x) { x = 0; char ch = g

【10.22校内测试】【二分】【二分图】【很像莫队的乱搞/树状数组】

Solution 谁能想到这道题卡读入??还卡了70pts??? 二分+$n^2$check就行了 Code #include<bits/stdc++.h> using namespace std; int n, m; int sum[2005][2005]; void read(int &x) { x = 0; char ch = getchar(); while(ch > '9' || ch < '0') ch = getchar(); while(ch >= '

【10.27校内测试】【可删堆+拓排】

Solution 有向图要找最长路径的话,可以想到拓扑序转移.正反跑两边处理出每个点离起点和终点的最大值.访问每条边就能统计出经过每条边最长路径的长度. 问题是怎么统计出删除每个点的影响? 拓扑排序后,可以发现,删除层数靠后的点会对前面产生影响,因为此时想统计前面的边存在的最长路就不能判掉经过这个点的路径,所以只能按拓扑序从前往后删点. 这里直接说做法吧,维护一个大根堆,储存当前枚举到的最长路径,首先把每个点离终点的最大值推入堆中.每枚举删除一个点,就把它对前面点有影响的路径删掉,更新答案后再把

【8.23校内测试】【贪心】【线段树优化DP】

$m$的数据范围看起来非常有问题??仔细多列几个例子可以发现,在$m<=5$的时候,只要找到有两行状态按位$&$起来等于$0$,就是可行方案,如果没有就不行. #include<iostream> #include<cstdio> #include<cstring> using namespace std; int cnt[1<<4+1], n, m; int main ( ) { freopen ( "prob.in",

【BZOJ-2892&amp;1171】强袭作战&amp;大sz的游戏 权值线段树+单调队列+标记永久化+DP

2892: 强袭作战 Time Limit: 50 Sec  Memory Limit: 512 MBSubmit: 45  Solved: 30[Submit][Status][Discuss] Description 在一个没有冬马的世界里,经历了学园祭后的春希着急着想要见到心爱的雪菜.然而在排队想见雪菜的fans太多了,春希一时半会凑不到雪菜面前. 作为高帅富,这样的问题怎么能难倒春希?春希从武也手中拿到了取自金闪闪宝库里的多啦A梦的传话筒,并且给每一个排队的fans都发了一个传话筒. 于

滑窗模板_双向队列

附上电科算法讲堂  https://www.bilibili.com/video/av23189029?t=641  (感谢那些讲课的美好的人们) /* * 注: 还有少许细节问题可能需要注意: 例如 总长度小于窗口长度的情况. * 和rGetMax rGetMin 下标小于零的情况 没有pass.. */ class HuaChuang_Max { public: struct cnobe { int id; int val; cnobe () {} cnobe (int _id, int _

【8.15校内测试】【队列】【manacher】

dp??不能确定转移状态.考虑用优先队列储存最优决策点,可是发现当前选择最优不能保证最后最优,在后面可以将之前用过的替换过来. 比如数据: 3 5 4 6 只储存a[i]来决策不能延展到后面的状态,因此每次选择过后把b[i]加入队列,下次选择最优时如果选择到了b[i],则表示用之前选择过的来替换到当前状态. 这里我开了两个优先队列. 1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #define l

Codeforces 526 E Transmitting Levels 滑窗(two points) + 枚举

题意:给你一个环形数组,让你求将这个数组分成 每段和 <= k 的最小段数. 解题思路:滑窗求的以 i结尾的 最长段长度,然后枚举最小的那个段长度中的值为终点搜索(因为必定有一个分割点在 最小段长上) 解题代码: 1 // File Name: e.cpp 2 // Author: darkdream 3 // Created Time: 2015年04月05日 星期日 14时55分31秒 4 5 #include<vector> 6 #include<list> 7 #in