NOI.ac2020省选模拟赛10
A.ball
problem
有n条轨道,在这些纵道之间有m条横道,当有球从某个轨道经过时,如果路过了一个横道,那么这个球就会通过横道到达相邻的一个纵道。
先放入m个横道,然后每次拿走一个。然后会有一些询问,询问当前从第i个轨道放下球最终会滚到哪个位置。
solution
用\(a_i\)表示第i个球滚到的位置。
如果没有横道那么答案就是\(a_i=i\)。发现从上到下每个横道的作用就是交换两个相邻球的答案。也就是说对于一个在\(i\)和\(i+1\)的横道,在经过这个横道时候\(a_i\)和\(a_{i+1}\)会交换。
可是置换不满足交换律。所以我们并不能先算出答案在拿掉横道之后更新答案。
发现一个在时间i拿走的横道\(t_i\)只会对\(t_i\)之前的询问产生左右。所以我们就开n个平衡树,第i个平衡树表示答案为i的询问,里面存放的是询问的时间。
然后从上到下处理每个横道,记第i个横道被拿走的时间为\(tim_i\),交换的位置是\((x,x+1)\)。那么就把x的平衡树里和x+1的平衡树里,时间点小于\(tim_i\)的询问交换。
code
/*
* @Author: wxyww
* @Date: 2020-06-10 09:01:29
* @Last Modified time: 2020-06-10 09:26:50
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
typedef long long ll;
const int N = 2000010;
#define ls TR[cur].ch[0]
#define rs TR[cur].ch[1]
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
struct node {
int pre,ch[2],val;
}TR[N];
int tot,root[N];
int getwh(int cur) {
return TR[TR[cur].pre].ch[1] == cur;
}
void rotate(int cur) {
int fa = TR[cur].pre,gr = TR[fa].pre,f = getwh(cur);
TR[cur].pre = gr;TR[gr].ch[getwh(fa)] = cur;
TR[TR[cur].ch[f ^ 1]].pre = fa;TR[fa].ch[f] = TR[cur].ch[f ^ 1];
TR[fa].pre = cur;TR[cur].ch[f ^ 1] = fa;
}
void splay(int &rt,int cur,int to) {
while(TR[cur].pre != to) {
if(TR[TR[cur].pre].pre != to) {
if(getwh(TR[cur].pre) == getwh(cur)) rotate(TR[cur].pre);
else rotate(cur);
}
rotate(cur);
}
if(!to) rt = cur;
}
void ins(int &rt,int cur,int val,int fa) {
if(!cur) {
cur = ++tot;
if(fa) TR[fa].ch[val > TR[fa].val] = cur;
TR[cur].val = val;
TR[cur].pre = fa;
splay(rt,cur,0);
return;
}
if(val < TR[cur].val) ins(rt,ls,val,cur);
else ins(rt,rs,val,cur);
}
int lst(int cur,int val) {//val的后继
// printf("%d %d\n",val,TR[cur].val);
int now = 1e9,ret = 0;
while(cur) {
// printf("%d\n",cur);
if(TR[cur].val < val) cur = rs;
else {
if(TR[cur].val < now) now = TR[cur].val,ret = cur;
cur = ls;
}
}
return ret;
}
int debug(int cur,int x) {
if(TR[cur].val > x) {
// cerr<<TR[cur].val<<" "<<x<<endl;
return 1;
}
int tag = 0;
if(ls) tag |= debug(ls,x);
if(rs) tag |= debug(rs,x);
return tag;
}
void solve(int l,int r,int x) {
int rt1 = lst(root[l],x),rt2 = lst(root[r],x);
splay(root[l],rt1,0);
splay(root[r],rt2,0);
// if(debug(TR[root[l]].ch[0],TR[root[l]].val)) cerr<<l<<endl;
// if(debug(TR[root[r]].ch[0],TR[root[r]].val)) cerr<<r<<endl;
swap(TR[root[l]].ch[0],TR[root[r]].ch[0]);
if(TR[TR[root[r]].ch[0]].val > TR[root[r]].val) cerr<<"!!!"<<endl;
TR[TR[root[l]].ch[0]].pre = root[l];
TR[TR[root[r]].ch[0]].pre = root[r];
}
int t[N],tim[N],ans[N];
void dfs(int cur,int x) {
if(!cur) return;
ans[TR[cur].val] = x;
dfs(ls,x);dfs(rs,x);
}
void dfss(int cur) {
if(!cur) return;
dfss(ls);
cerr<<cur<<" ";
dfss(rs);
}
int main() {
// freopen("1.in","r",stdin);
// freopen("bug.in","r",stdin);
// freopen("A.out","w",stdout);
int n = read(),m = read();
for(int i = 1;i <= m;++i) {
t[i] = read();
}
int Q = read();
for(int i = 1;i <= n;++i)
ins(root[i],root[i],Q + 1,0);
for(int i = 1;i <= m;++i) tim[i] = Q + 1;
for(int i = 1;i <= Q;++i) {
int opt = read();
if(opt == 1) {
int x = read();
tim[x] = i;
}
else {
int x = read();
ins(root[x],root[x],i,0);
}
}
for(int i = 1;i <= m;++i)
solve(t[i],t[i] + 1,tim[i]);
for(int i = 1;i <= n;++i) dfs(root[i],i);
// dfss(root[165]);
for(int i = 1;i <= Q;++i)
if(ans[i]) printf("%d\n",ans[i]);
return 0;
}
B.C
problem
给出一个\(n\)个点,\(m\)条边的无向图,记第\(i\)个点的度数为\(d_i\)。删去一些边,使得剩下的边数不超过\(\lceil\frac{n+m}{2}\rceil\)。切删边后第\(i\)个点的度数\(f_i\)要满足\(f_i\ge \lceil\frac{d_i}{2}\rceil\)
solution
一种不知道对不对的贪心方法,将每条边按照较小度数的端点的度数从大到小排序。然后优先删前面的,能删就删。
code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<set>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
const int N = 1000010;
ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
struct node {
int u,v,id;
}e[N];
int du[N];
bool cmp(const node &A,const node &B) {
return min(du[A.u],du[A.v]) > min(du[B.u],du[B.v]);
}
int ans[N];
int main() {
int n = read(),m = read();
for(int i = 1;i <= m;++i) {
e[i].u = read();e[i].v = read();
du[e[i].u]++;du[e[i].v]++;
e[i].id = i;
}
for(int i = 1;i <= n;++i)
du[i] = (du[i] >> 1);
sort(e + 1,e + m + 1,cmp);
for(int i = 1;i <= m;++i) {
if(du[e[i].u] && du[e[i].v]) {
du[e[i].u]--;du[e[i].v]--;
}
else ans[e[i].id] = 1;
}
for(int i = 1;i <= m;++i) printf("%d ",ans[i]);
return 0;
}
C.寄蒜几盒
problem
有一个\(n\)(\(n\)为偶数)条边的凸多边形。选择每对相对的边,然后将他们延长,这样将平面分为几部分,然后把凸多边形所在的平面染色。
每次询问一个点,回答这个点是不是被染色。强制在线。
solution
将每个点看做障碍,如果一个点不能被染色,当且仅当他能看到至少\(\frac{n}{2}\)条边。验证一下发现这个结论是对的,怎么想出来的我也不知道。。
然后我们先以\(0\)号边和\(n/2\)号边中能看到的为起点,然后二分出一个能看到的与不能看到的交界的位置。从这个位置在判断一下是不是能看到\(n/2\)条边就行了。
问题在于如何判断一个点能不能看到一条边。因为凸多边形上的点都是逆时针给出的。一个点能看到一条边\((i,i+1)\),当且仅当与点\(i\)之间的连边斜率小于与点\(i+1\)之间的斜率。
code
/*
* @Author: wxyww
* @Date: 2020-06-11 07:25:11
* @Last Modified time: 2020-06-11 19:32:55
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 100010;
#define int ll
ll read() {
ll x = 0,f = 1;char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1; c = getchar();
}
while(c >= '0' && c <= '9') {
x = x * 10 + c - '0'; c = getchar();
}
return x * f;
}
ll x[N],y[N];
bool check(ll x1,ll y1,ll x2,ll y2,ll x3,ll y3) {
return (__int128)(y1 - y3) * (x2 - x3) >= (__int128)(y2 - y3) * (x1 - x3);
}
signed main() {
ll T = read(),cnt = 0,n = read();
for(int i = 0;i < n;++i) x[i] = read(),y[i] = read();
int Q = read();
while(Q--) {
ll A = read(),B = read();
if(T) {
A ^= cnt * cnt * cnt;
B ^= cnt * cnt * cnt;
}
int t1 = check(x[0],y[0],x[1],y[1],A,B);
int t2 = check(x[n / 2],y[n / 2],x[(n / 2 + 1) % n],y[(n / 2 + 1) % n],A,B);
if(t1 && t2) {
puts("NE");continue;
}
if(!t1 && !t2) {
puts("DA");cnt++;continue;
}
// printf("%d %d\n",t1,t2);
int k = 0;
if(t2) k = n / 2;
int l = 0,r = n / 2;
int ret = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(check(x[(k + mid) % n],y[(k + mid) % n],x[(k + mid + 1) % n],y[(k + mid + 1) % n],A,B)) ret = k + mid,l = mid + 1;
else r = mid - 1;
}
ret %= n;
// printf("%lld %lld\n",x[ret],y[ret]);
// cout<<ret<<endl;
if(check(x[(ret - n / 2 + n + 1) % n],y[(ret - n / 2 + n + 1) % n],x[(ret - n / 2 + 2 + n) % n],y[(ret - n / 2 + 2 + n) % n],A,B)) {
puts("NE");
}
else {
cnt++;puts("DA");
}
}
return 0;
}