雅礼集训 2017 Day2

mac2022-06-30  12

雅礼集训 2017 Day2

水箱

题目描述:

给出一个长度为\(n\)宽度为1,高度无限的水箱,有个\(n-1\)挡板将其分为\(n\)\(1 - 1\)的小格,然后向每个小格中注水,水如果超过挡板就会溢出到挡板的另一边,这里的水是满足物理定律的(在无挡板阻拦的情况下会向低处流),现在有个条件 \(m(i,y,k)\),表示从左到右数的第\(i\)个格子中,在高度为\(y+0.5\)的地方是否有水,\(k=1\)表示有水,\(k=0\)表示没有水,请求出这\(m\)个条件最多能同时满足多少个条件。本题有多组数据。

题解:

这题妙啊,膜拜了1h题解。首先我们看一看\(n=1\)的情况,这个时候不用理挡板直接枚举水位即可,将所有条件按高度排序,每次决策是否放水即可。那么,考虑加入挡板,对于一个高度大于当前水位的挡板,可以直接将其视为无穷大,这时,这些挡板就将水箱划分成许多互不影响的子问题。那么每当水位淹过一个挡板时,就相当于将两个子问题合并,由于我们需要维护的东西十分少,所以可以直接加法合并。具体实现:记\(f_x\)表示当前区域内有多少条件要求一定有水,\(g_x\)表示当前区域的答案,转移:

1、当前操作为“加入一个挡板”:那么就将左右两个连通块合并。

2、当前操作为“有水”:那么可以将水放到当前水位,也可以不管\(f_x ++,g_x = max(g_x,f_x)\)

3、当前操作为“没水”:那么就直接累加进答案即可,\(g_x++\)

!!!操作顺序十分重要!!!

代码:

#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn = 100000 + 10; namespace IO{ const int S=(1<<20)+5; char buf[S],*H,*T; inline char Get() { if(H==T) T=(H=buf)+fread(buf,1,S,stdin); if(H==T) return -1;return *H++; } inline int rd() { int x=0;char c=Get(); while(!isdigit(c)) c=Get(); while(isdigit(c)) x=x*10+c-'0',c=Get(); return x; } } int n, m, cnt; inline int cmp(int x, int y) { if(x == -1) return 1; if(y == -1) return 0; return x < y; } struct qnode{ int kd, x, y; qnode() {} qnode(int _kd, int _x, int _y):kd(_kd), x(_x), y(_y) {} friend bool operator < (qnode a, qnode b) { if(a.y != b.y) return a.y < b.y; return cmp(a.kd,b.kd); } }q[maxn << 1]; //bcj int sfa[maxn << 1], f[maxn << 1], g[maxn << 1]; int findfa(int u) { if(sfa[u] == u) return u; else return sfa[u] = findfa(sfa[u]); } inline void solve() { using IO::rd; // scanf("%d%d", &n, &m); cnt = 0; n = rd(); m = rd(); for(int i = 1;i < n;i ++) { int x; x = rd(); // scanf("%d", &x); q[++ cnt] = qnode(-1,i,x); } for(int i = 1;i <= m;i ++) { int op, x, y; x = rd(); y = rd(); op = rd(); // scanf("%d%d%d", &x, &y, &op); q[++ cnt] = qnode(op,x,y); } sort(q + 1,q + cnt + 1); for(int i = 1;i <= n + 1;i ++) sfa[i] = i; memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); int Ans = 0; for(int i = 1;i <= cnt;i ++) { int kd = q[i].kd; if(kd == -1) { int f1 = findfa(q[i].x); int f2 = findfa(q[i].x + 1); sfa[f2] = f1; f[f1] += f[f2]; g[f1] += g[f2]; Ans = max(Ans,g[f1]); } if(kd == 0) { int f1 = findfa(q[i].x); g[f1] ++; Ans = max(Ans,g[f1]); } if(kd == 1) { int f1 = findfa(q[i].x); f[f1] ++; g[f1] = max(g[f1],f[f1]); Ans = max(Ans,g[f1]); } } printf("%d\n", Ans); } int main() { int cas; //scanf("%d", &cas); cas = IO::rd(); while(cas --) solve(); return 0; }

棋盘游戏

题目描述:

给定一个棋盘,并有若干位置是障碍。然后Alice先钦定棋子的初始位置,接着从Bob开始轮流移动棋子,要求不能重复走,不能走障碍点,谁无法移动则输掉游戏,请统计所有Alice必胜的点,并输出。

题解:

首先无脑黑白染色一下,然后我们就得到了一个二分图。那么考虑必胜策略:

1、如果存在完美匹配,Alice必败。因为每次Bob只需要走匹配边,那么Alice只能走非匹配边,因为存在完美匹配,所有Bob总有匹配边可以走,但是Alice就比较凉了~

2、如果不存在完美匹配,那么我们思考一下奇偶性,如果一个点不一定在最大匹配上,那么它所在的所有交错轨长度应该都是偶数,那么Alice就比胜了。

所以问题就变成如何求二分图中那些点不一定在最大匹配上。怎么求呢,我们可以先求出任意一个最大匹配,然后对每一个未匹配点找交错轨,如果找到了交错轨,那么它就不一定在最大匹配上。

代码:

#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; const int maxn = 100 + 5; const int N = 10100; const int E = 200000 + 10; const int xx[4] = {-1,0,1,0}; const int yy[4] = {0,1,0,-1}; const int INF = 0x3f3f3f3f; int n, m, tot, id[maxn][maxn], flow; int h[N], ecnt, S, T; struct enode{ int v, n, w, op; enode() {} enode(int _v, int _n, int _w, int _op):v(_v), n(_n), w(_w), op(_op) {} }e[E << 1]; char s[maxn][maxn]; inline void addedge(int u, int v, int w) { // cout << u << ' ' << v << endl; ecnt ++; e[ecnt] = enode(v,h[u],w,ecnt + 1); h[u] = ecnt; ecnt ++; e[ecnt] = enode(u,h[v],0,ecnt - 1); h[v] = ecnt; } int level[N], q[E], cur[N]; inline int bfs() { int head = 1, tail = 1; memset(level,0,sizeof(level)); q[tail] = S; level[S] = 1; while(head <= tail) { int u = q[head]; head ++; for(int i = h[u];~ i;i = e[i].n) { int v = e[i].v; if(!level[v] && e[i].w > 0) { level[v] = level[u] + 1; q[++ tail] = v; if(v == T) return 1; } } } return 0; } int dfs(int u, int c) { if(u == T || c == 0) return c; int tmp = 0; for(int i = cur[u];~ i;i = e[i].n) { cur[u] = i; int v = e[i].v; if(level[v] == level[u] + 1) { int x = dfs(v,min(c,e[i].w)); if(x) { tmp += x; c -= x; e[i].w -= x; e[e[i].op].w += x; if(!c) break; } } } if(!tmp) level[u] = -1; return tmp; } inline void dicnic() { flow = 0; while(bfs()) { for(int i = S;i <= T;i ++) cur[i] = h[i]; flow += dfs(S,INF); } } int vis[N]; void dfs1(int u) { vis[u] |= 1; for(int i = h[u];~ i;i = e[i].n) { int v = e[i].v; if(!e[i].w || (vis[v] & 1)) continue; dfs1(v); } } void dfs2(int u) { vis[u] |= 2; for(int i = h[u];~ i;i = e[i].n) { int v = e[i].v; if(!e[e[i].op].w || (vis[v] & 2)) continue; dfs2(v); } } int main() { // freopen("data.in","r",stdin); memset(h,-1,sizeof(h)); memset(vis,0,sizeof(vis)); scanf("%d%d", &n, &m); tot = 0; for(int i = 1;i <= n;i ++) scanf("%s", s[i] + 1); for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m;j ++) { if(s[i][j] == '#') continue; id[i][j] = ++ tot; } } S = 0; T = tot + 1; for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m;j ++) { if(s[i][j] == '#') continue; if(!((i + j) & 1)) addedge(S,id[i][j],1); else addedge(id[i][j],T,1); } } for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m;j ++) { if((i + j) & 1) continue; if(s[i][j] == '#') continue; for(int k = 0;k < 4;k ++) { int dx = i + xx[k]; int dy = j + yy[k]; if(dx < 1 || dy < 1 || dx > n || dy > m) continue; if(s[dx][dy] == '#') continue; addedge(id[i][j],id[dx][dy],1); } } } dicnic(); dfs1(S); dfs2(T); int Ans = 0; for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m;j ++) { if(s[i][j] == '#') continue; int x = id[i][j], k; if((i + j) & 1) k = 2; else k = 1; if(vis[x] == k) Ans ++; } } printf("%d\n", Ans); for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m;j ++) { if(s[i][j] == '#') continue; int x = id[i][j], k; if((i + j) & 1) k = 2; else k = 1; if(vis[x] == k) printf("%d %d\n", i, j); } } return 0; }

线段游戏

题目描述:

给出若干条线段,要求支持两个操作: 1、加入一条线段

2、询问与\(x = k\)的交点最高的线段是哪条,如果有多条最高的就输出编号最小的那个。

题解:

好吧,这玩意儿是个模板题,我好菜啊。所以这玩意儿就是个裸的李超线段树,,,

转载于:https://www.cnblogs.com/ezhjw/p/10025774.html

最新回复(0)