2022NOIPA层联测12
佛说,退一步海阔天空啊,亲。
可是,当你一步一步退下来,才会发现,你面前那广阔的海洋,高远的天空,可能都不再属于你了。而当你渐渐归顺于庸庸碌碌、汲汲营营的人生,才会懂得,那都是你妥协的代价。
A. 染色
想到了二分图?mex?再模一下发现1234就是答案!4是最小的合数,它的倍数当然也是合数,差4个一定可以分到一组。样例太赞了,否则我还得考虑n等于几的时候是k取4的边界,最小值这不就直接有了吗!
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 3;
int n;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
n = read();
if(n == 1)
{
printf("1\n");
printf("1\n"); exit(0);
}
if(n == 2)
{
printf("1\n");
printf("1 1\n"); exit(0);
}
if(n == 3)
{
printf("2\n");
printf("1 1 2\n"); exit(0);
}
if(n == 4)
{
printf("2\n");
printf("1 1 2 2\n"); exit(0);
}
if(n == 5)
{
printf("3\n");
printf("1 1 2 2 3\n"); exit(0);
}
if(n == 6)
{
printf("3\n");
printf("1 1 2 2 3 3\n"); exit(0);
}
printf("4\n");
for(int i=1; i<=n; i++)
{
int col = (i-1) % 4;
printf("%d ", col+1);
}
printf("\n");
return 0;
}
B. 序列
呃,没看懂那个函数形状是怎么搞的……大概都说是三分我就三分吧,好多奇奇妙妙的三分写法……我本来想直接取最大值因为样例是这样的。还有忽然发现要找到这个最大值不用二分,求个和作除法就好了,好2啊。
check就是利用贪心策略,若a[i] < a[j],则b[i] >= b[j],所以确定好均摊的范围剩下的能加的就加。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 3;
ll T, n, m, k, D, sum, l, r, pos, ans, a[maxn];
inline ll read()
{
ll x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
//贪心策略:若ai <= aj,则bi >= bj
//从小到大枚举a对应的b,每一步都使当前的b大到上限
//我本来以为均摊到最大最优,结果没分了。。事实上均摊成多少需要分
ll check(int x)//初始所有bi相等,分出这个值是多少
{
ll ret = D-x*sum, ans = k*x+m*x;
for(int i=1; i<=m; i++)
{
if(ret < a[i]) return ans;
ll p = min(ret/a[i], n-x);//找到给bi加几个1,b每多1乘积和就多一个ai
ans += p; ret -= a[i] * p;
}
return ans;
}
int main()
{
freopen("array.in", "r", stdin);
freopen("array.out", "w", stdout);
T = read();
while(T--)
{
n = read(); m = read(); k = read(); D = read();
sum = 0; ans = 0;
for(int i=1; i<=m; i++)
{
a[i] = read(); sum += a[i];
}
sort(a+1, a+m+1);
l = 0; r = D/sum;
/* while(r-l>10000)//我鹤了个什么鬼?我不知道这是三分还是二分了。。
{
ll mid = (l + r) >> 1;
if(check(mid) < check(mid+1)) l = mid + 1;
else r = mid;
}
for(ll i=l; i<=r; i++) ans = max(ans, check(i));*/
while(l <= r)//我还是鹤个正经的三分吧
{
if(r - l <= 100)
{
for(ll i=l; i<=r; i++) ans = max(ans, check(i));
break;
}
ll lmid = (r - l) / 3 + l;
ll rmid = r - (r - l) / 3;
ll al = check(lmid), ar = check(rmid);
ans = max(ans, max(al, ar));
if(al < ar) l = lmid + 1;
else r = rmid - 1;
}
printf("%lld\n", ans);
}
return 0;
}
C. 树上询问
8:01 TLE 75就交上去了,hhh我是第一个提交的,今日开题顺序3214,8:15模T2模不出来做T1去了,8:22交了发现没加freopen,8:24又交了一版A了,至此今天得分的题都拿到了分……
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 3;
int n, m, ans, t;
int dep[maxn], fa[maxn], siz[maxn], top[maxn], son[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct node
{
int next, to;
}a[maxn<<1];
int head[maxn], len;
void add(int x, int y)
{
a[++len].to = y; a[len].next = head[x];
head[x] = len;
}
void find_heavy_edge(int u, int fat, int depth)
{
siz[u] = 1;
dep[u] = depth;
fa[u] = fat;
son[u] = 0;
int maxsize = 0;
for(int i=head[u]; i; i=a[i].next)
{
int v = a[i].to;
if(dep[v]) continue;
find_heavy_edge(v, u, depth+1);
siz[u] += siz[v];
if(siz[v] > maxsize)
{
maxsize = siz[v];
son[u] = v;
}
}
}
void connect_heavy_edge(int u, int ancestor)
{
top[u] = ancestor;
if(son[u])
{
connect_heavy_edge(son[u], ancestor);
}
for(int i=head[u]; i; i=a[i].next)
{
int v = a[i].to;
if(v == fa[u] || v == son[u]) continue;
connect_heavy_edge(v, v);
}
}
int LCA(int x, int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
return x;
}
int main()
{
freopen("query.in", "r", stdin);
freopen("query.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<n; i++)
{
int x = read(), y = read();
add(x, y); add(y, x);
}
find_heavy_edge(1, 1, 1);
connect_heavy_edge(1, 1);
while(m--)
{
ans = 0;
int l = read(), r = read(), lca = LCA(l, r);
int dis = dep[l] - dep[lca] + dep[r] - dep[lca];
t = 0;
while(l != lca)
{
l = fa[l]; t++;
//printf("l = %d t = %d\n", l, t);
if(t == l) ans++;
}
t = dis;
while(r != lca)
{
//printf("r = %d t = %d\n", r, t);
if(r == t) ans++;
r = fa[r]; t--;
}
printf("%d\n", ans);
}
return 0;
}
将链拆为两端a到lca(a, b)与lca(a, b)到b,a到lca(a, b)的答案就是dep[a] - dep[x] = x的x的个数,也就是说dep[x] + x - dep[a]的x的个数,b到lca(a, b)的答案就是dis - (dep[b] - dep[x]) = x的x的个数,dep[x] - x = dep[b] - dis,注意数组下标不能为负数。
套路:将询问离线,做一个树上前缀和,树上差分查找点到根路径上又多少个点满足条件,dfs出x的时候把x的贡献删掉。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 3;
int n, m;
int dep[maxn], fa[maxn], siz[maxn], top[maxn], son[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
struct node
{
int next, to;
}a[maxn<<1];
int head[maxn], len;
void add(int x, int y)
{
a[++len].to = y; a[len].next = head[x];
head[x] = len;
}
void find_heavy_edge(int u, int fat, int depth)
{
siz[u] = 1;
dep[u] = depth;
fa[u] = fat;
son[u] = 0;
int maxsize = 0;
for(int i=head[u]; i; i=a[i].next)
{
int v = a[i].to;
if(dep[v]) continue;
find_heavy_edge(v, u, depth+1);
siz[u] += siz[v];
if(siz[v] > maxsize)
{
maxsize = siz[v];
son[u] = v;
}
}
}
void connect_heavy_edge(int u, int ancestor)
{
top[u] = ancestor;
if(son[u])
{
connect_heavy_edge(son[u], ancestor);
}
for(int i=head[u]; i; i=a[i].next)
{
int v = a[i].to;
if(v == fa[u] || v == son[u]) continue;
connect_heavy_edge(v, v);
}
}
int LCA(int x, int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
return x;
}
struct que {int id, opt, ad, k;};
vector<que> v[maxn];
int tmp1[maxn*3], tmp2[maxn*3], base, ans[maxn];
void solve(int x)
{
tmp1[x+dep[x]]++;
tmp2[dep[x]-x+base]++;
for(que y : v[x])
{
if(y.ad & 1) ans[y.id] += y.opt* tmp1[y.k];
else ans[y.id] += y.opt * tmp2[y.k+base];
}
for(int i=head[x]; i; i=a[i].next)
{
int v = a[i].to;
if(v == fa[x]) continue;
solve(v);
}
tmp1[x+dep[x]]--;
tmp2[dep[x]-x+base]--;
}
int main()
{
freopen("query.in", "r", stdin);
freopen("query.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<n; i++)
{
int u = read(), v = read();
add(u, v); add(v, u);
}
base = n + 5;
find_heavy_edge(1, 0, 1);
connect_heavy_edge(1, 1);
for(int i=1; i<=m; i++)
{
int l = read(), r = read(), lc = LCA(l, r);
int s = dep[l] + dep[r] - dep[lc] - dep[lc];
v[l].push_back({i, 1, 1, dep[l]});
v[r].push_back({i, 1, 2, dep[r]-s});
v[lc].push_back({i, -1, 1, dep[l]});
v[fa[lc]].push_back({i, -1, 2, dep[r]-s});
}
solve(1);
for(int i=1; i<=m; i++) printf("%d\n", ans[i]);
return 0;
}
D. 网络
n = 3输啥都行直接送分。m <= 20二进制枚举。
我有一种构造方案可以构造n = 4的情况至少剩下两个有电流,先发现构造n = 3的情况一定可以剩下两个,4的话就相当于把第一条需要汇入的线去掉,这个可以正推,每条路的方向只与下一条路涉及哪两个点有关,电流的方向就是躲掉其他情况汇入,因为第一次平衡一定会有一个损失,n = 3的时候要剩两条那以后就不能再有有电流向有电流汇入的情况,那么对于将会有可能成为汇入线的电线,它需要先把它的电流导出到没有电流的那条导线上躲避电流,由于上一步合法,推下去后面的也合法,把涉及到指定要删除的线提前删掉就可以把n = 4变成n = 3。为什么我考虑了n = 3剩两个?因为我看成了向上取整。
然而我没审题,我以为向上就是n变小的方向,向下就是n变大的方向,于是输入的时候比较大小swap了一下,30->10。审题错误*2,想到部分分的解法负正得负……
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e6 + 3;
int n, m, u[maxn], v[maxn], lim;
bool vis[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
void solvex()
{
printf("YES\n");
for(int i=1; i<m; i++)
{
if(u[i] == u[i+1]||u[i] == v[i+1])
{
printf("1"); //vis[u[i]] = 0; vis[v[i]] = 1;
}
else
{
printf("0"); //if(vis[v[i]]) vis[v[i]] = 0, vis[u[i]] = 1;
}
}
printf("0\n");
}
void solve3()
{
printf("YES\n");
for(int i=1; i<=m; i++)
{
printf("0");
}
printf("\n");
}
struct node
{
int u, v, id;
}ls[maxn];
int cnt;
bool ans[maxn];
void solve4()
{
printf("YES\n");
printf("1"); int sp = u[1];
for(int i=2; i<=m; i++)
{
if(u[i] == sp) {ans[i] = 1; continue;}
if(v[i] == sp) {ans[i] = 0; continue;}
ls[++cnt].u = u[i], ls[cnt].v = v[i]; ls[cnt].id = i;
//printf("ls[%d].u = %d ls[%d].v = %d\n", cnt, ls[cnt].u, cnt, ls[cnt].v);
}
for(int i=1; i<cnt; i++)
{
if(ls[i].u == ls[i+1].u || ls[i].u == ls[i+1].v)
{
ans[ls[i].id] = 1;
}
else
{
ans[ls[i].id] = 0;
}
}
for(int i=2; i<m; i++) printf("%d", ans[i]);
printf("0\n");
}
int Max;
void check(int num)
{
for(int i=1; i<=n; i++) ans[i] = 1;
for(int i=1; i<=m; i++)
{
if(num & (1<<i-1))
{
if(ans[u[i]]) ans[u[i]] = 0, ans[v[i]] = 1;
}
else
{
if(ans[v[i]]) ans[v[i]] = 0, ans[u[i]] = 1;
}
}
cnt = 0;
for(int i=1; i<=n; i++)
{
if(ans[i]) cnt++;
}
if(cnt >= lim)
{
printf("YES\n");
for(int i=1; i<=m; i++)
{
if(num & (1<<i-1)) printf("1");
else printf("0");
}
exit(0);
}
}
void solvesml()
{
Max = 1 << m;
for(int i=0; i<Max; i++)
{
check(i);
}
printf("NO\n");
}
int main()
{
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
n = read(); m = read();
//for(int i=1; i<=n; i++) vis[i] = 1;
for(int i=1; i<=m; i++)
{
u[i] = read(); v[i] = read();
//if(u[i] > v[i]) swap(u[i], v[i]);
}
if(n == 3)
{
solve3(); exit(0);
}
if(n == 4)
{
solve4(); exit(0);
}
if(m <= 20)
{
lim = n / 2;
solvesml(); exit(0);
}
solvex();
return 0;
}
听说过一字千金吗?就是写得太好一个字都改不了。用它来形容Chen_jr的题解正好qwq我改了是因为懒的打那么多字qwq
我们只需要保证带电的>=n/2,每一次的平衡,实际上是一个x,y不能同时带电的操作,于是我们把满足 不能同时带电 的两根电线看成一个二元组。初始随便定,先正推确定一个合法的终止状态和过程中的状态变化,再倒推一遍撤销每次配对的影响。
出现一条x,y的平衡器是,假设原来的配对为(x, a)(y, b),此时x,y不能同时带电,所以新的配对为(x, y)(a, b)。推回到一条边x, y时,如果此时y带电(经过x,y这条边之后y还有电),那么为1,否则为0。撤销该次配对时,由于只能改变x, y的电性,所以需要根据之前的x, y的电性来重新确定x, y。
以上是涉及到4根电线的情况,3根电线同理。
代码都是鹤!T3也是鹤!!
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e6 + 3;
struct edge {int x, y;} e[maxn];
bool elc[maxn];
int n, m, oth[maxn], ans[maxn], opt[maxn][2];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
int main()
{
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=m; i++) e[i].x = read(), e[i].y = read();
for(int i=1; i<=n; i+=2) oth[i] = i+1;
if(n & 1) oth[n] = n;
for(int i=2; i<=n; i+=2) oth[i] = i-1;
for(int i=1; i<=m; i++)
{
int x = e[i].x, y = e[i].y;
int ox = oth[x], oy = oth[y];
if(ox == y) continue;
if(x == ox)
{
oth[y] = x;
oth[x] = y;
oth[oy] = oy;
opt[i][1] = oy;
}
else if(y == oy)
{
oth[y] = x;
oth[x] = y;
oth[ox] = ox;
opt[i][0] = ox;
}
else
{
oth[ox] = oy; oth[oy] = ox;
oth[x] = y; oth[y] = x;
opt[i][0] = ox, opt[i][1] = oy;
}
}
for(int i=1; i<=n; i++) elc[i] = !elc[oth[i]];
for(int i=m; i>=1; i--)
{
int x = e[i].x, y = e[i].y;
int ox = opt[i][0], oy = opt[i][1];
if(elc[y]) ans[i] = 1;
if(ox && oy)
{
if(elc[ox]) elc[x] = 0, elc[y] = 1;
else elc[x] = 1, elc[y] = 0;
}
else if(ox)
{
elc[x] = 0;
elc[ox] = elc[y] = 1;
}
else
{
elc[y] = 0;
elc[oy] = elc[x] = 1;
}
}
printf("YES\n");
for(int i=1; i<=m; i++) printf("%d", ans[i]);
return 0;
}
dfs出60分的大佬简直太巨辣%%%%%我只有模%%%%%
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e6 + 3;
int u[maxn], v[maxn], n, m;
bool ele[maxn], as[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch^48);
ch = getchar();
}
return x * f;
}
bool dfs(int now, int lef)
{
if(lef < (n+1)/2) return 0;
if(now > m) return 1;
if(ele[u[now]] && ele[v[now]])
{
as[now] = 1;
ele[u[now]] = 0;
if(dfs(now+1, lef-1)) return 1;
as[now] = 0;
ele[u[now]] = 1, ele[v[now]] = 0;
if(dfs(now+1, lef-1)) return 1;
ele[u[now]] = ele[v[now]] = 1;
}
else if(ele[u[now]] && !ele[v[now]])
{
as[now] = 0;
if(dfs(now+1, lef)) return 1;
as[now] = 1;
ele[u[now]] = 0; ele[v[now]] = 1;
if(dfs(now+1, lef)) return 1;
ele[u[now]] = 1; ele[v[now]] = 0;
}
else if(!ele[u[now]] && ele[v[now]])
{
as[now] = 1;
if(dfs(now+1, lef)) return 1;
as[now] = 0;
ele[v[now]] = 0; ele[u[now]] = 1;
if(dfs(now+1, lef)) return 1;
ele[v[now]] = 1; ele[u[now]] = 0;
}
else
{
as[now] = 0;
if(dfs(now+1, lef)) return 1;
}
return 0;
}
int main()
{
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
n = read(); m = read();
for(int i=1; i<=m; i++) u[i] = read(), v[i] = read();
fill(ele+1, ele+1+n, 1);
dfs(1, n);
printf("YES\n");
for(int i=1; i<=m; i++) printf("%d", as[i]);
return 0;
}