雅礼集训 2017 Day2
雅礼集训 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\)的交点最高的线段是哪条,如果有多条最高的就输出编号最小的那个。
题解:
好吧,这玩意儿是个模板题,我好菜啊。所以这玩意儿就是个裸的李超线段树,,,