NOIP2023模拟7,8联测28,29
NOIP2023模拟7联测28
T1
看到了有向无环图,很容易让人想到拓扑。
设 \(f_i\) 表示经过节点 \(i\) 路径,这条路径上的关键点个数的最大值。
如果有一个点满足 \(f_i=k\), 那么答案就是 \(Yes\),否则就是 \(No\),这个显然。
转移就是从所有能到达 \(i\) 的节点转移,取 \(\max\)。这个可以通过拓扑实现。
复杂度为 \(\Theta(\sum m)\)。
Code
#include <iostream>
#include <cstdio>
#include <queue>
const int N=1e6+10;
using namespace std;
inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>'9' || ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}
inline void write(bool x) {
if(x) cout<<"Yes";
else cout<<"No";
putchar('\n');
}
int T;
int n, m, k, cnt_edge;
struct edge {
int next, to;
}e[N<<1];
int head[N], in[N];
int vis[N], val[N];
void add_edge(int u,int v) {
e[++cnt_edge].to=v;
e[cnt_edge].next=head[u];
head[u]=cnt_edge;
in[v]++;
}
inline void init() {
for(int i=1;i<=n;i++) {
head[i]=in[i]=0;
vis[i]=val[i]=0;
}
for(int i=1;i<=cnt_edge;i++) {
e[i].to=e[i].next=0;
}
n=cnt_edge=0;
}
queue <int> q;
void topu() {
for(int i=1;i<=n;i++) {
if(!in[i]) q.push(i);
}
while(!q.empty()) {
int now=q.front(); q.pop();
vis[now]+=val[now];
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
vis[v]=max(vis[v], vis[now]);
in[v]--;
if(!in[v]) q.push(v);
}
}
bool flag=0;
for(int i=1;i<=n;i++) {
if(vis[i]==k) {
flag=1;
break;
}
}
write(flag);
}
int main() {
T=read();
while(T--) {
init();
n=read(), m=read();
for(int i=1;i<=m;i++) {
int u=read(), v=read();
add_edge(u, v);
}
k=read();
for(int i=1;i<=k;i++) {
int x=read();
val[x]=1;
}
topu();
}
return 0;
}
T2
Part1
区间修改不考虑,考虑差分,原序列为 \(a_i\), 设 \(b_i=a_i \oplus a_{i-1}\), 那么易证当且仅当所有的 \(b_i\) 都为零时,所有的 \(a_i\) 也等于零。对 \(a_i\) 的区间修改,在差分数组上就是单点修改。一次修改一个值或者两个值。
Part2
我们把 \(n\) 个数看成 \(n\) 个点,两个数的修改看成两个点之间的连边,一个点的修改看成自环。那么最终的答案就是总边数,我们想让边数最小。
我们选 \(x\) 个点组成一个连通块,考虑最优策略下要连多少条边,发现上界为 \(x\)(即 \(x\) 个自环),下界为 \(x-1\),组成了一棵树,考虑什么情况下可以达到下界。有结论:
- 当且仅当连通块内的点一开始的异或和为 \(0\) 时,才可以达到下界。
必要性:因为进行了 \(x-1\) 次操作,那么每次操作必然是修改两个,总的异或和不变,要想通过修改使其都变成 \(0\), 那么这几个数一开始的异或和必然为 \(0\)。
充分性:如果异或和为 \(0\),那么可以把这几个点穿成一条链,每次把链头通过异或自己的方式把自己变为 \(0\),这样最后一个数就变成了这几个数的异或和,为 \(0\)。
那么我们就是想让这样的连通块最多,设其为 \(x\),答案就是 \(n-x\)。
Part3
现在问题变成了:给定一个序列,把其划分成若干个子序列,最大化异或和为 \(0\) 的个数。可以状压解决,每次转移枚举子集。用到了一个枚举子集的技巧。
复杂度证明
考虑每个状态被枚举了几次。
设当前状态集合为 \(T\),那么被枚举的次数就是 \(2^{n-|T|}\)
由此可得总的次数为:
二项式反演即为:\(3^n\)。
复杂度为 \(\Theta(3^n)\)。
Code
#include <iostream>
#include <cstdio>
#define int long long
const int N=18;
const int M=(1<<N)+10;
using namespace std;
inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>'9' || ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}
inline void write(int x) {
cout<<x; putchar('\n');
}
int n, ans;
int a[N], b[N], dp[M];
bool f[M];
signed main() {
n=read();
for(int i=1;i<=n;i++) {
a[i]=read(); b[i]=a[i]^a[i-1];
}
for(int s=0;s<(1<<n);s++) {
int sum=0;
for(int i=1;i<=n;i++) {
if(s & (1<<(i-1))) sum^=b[i];
}
if(!sum) f[s]=1;
}
for(int s=1;s<(1<<n);s++) {
for(int t=s;t;t=(t-1)&s) {
if(f[t]) {
dp[s]=max(dp[s], dp[s^t]+1);
}
}
}
write(n-dp[(1<<n)-1]);
return 0;
}
T3
Part1
先考虑特殊性质 \(A\),发现 \(b\) 是给定的,说明答案的后半部分的贡献是固定不变的,那么只要保证 \(dis(a,x)\) 最小即可,直接点分树即可,在每个重心维护其点分树子树内具其最近的点的距离,暴力跳父亲修改并查询即可,复杂度为 \(\Theta(n \log n)\)。
Part2
原题又多了一部分贡献,我们可以考虑固定一部分贡献,另一部分取 \(\min\),考虑点分治。把询问和修改离线下来。点分树作为辅助。
设当前点分治的分治重心为 \(u\),我们把答案分为 \(4\) 部分,\(dis(x,u)\),\(dis(a,u)\),还有两部分为 \(dis(y,b)\),在点分树上把它拆成了 \(2\) 部分。其中 \(dis(x,u)\),\(dis(a,u)\) 是固定的。
对于修改操作 \((a,b)\),让 \(b\) 在点分树上暴力跳父亲,设当前跳到了 \(P\),那么就用 \(dis(u,a)+dis(P,b)\) 更新 \(P\) 节点的权值 \(val_P\),这相当于在给 其中一部分取 \(\min\)。
对于查询操作 \((x,y)\),让 \(y\) 在点分树上暴力跳父亲,设当前跳到了 \(P\),那么就用 \(dis(y,P)+val_P\) 更新答案,最后加上已经固定 \(dis(x,u)\) 就行了。
因为是取 \(\min\) 操作,而且还没有负边权,所以不用容斥,直接算就可以了。
复杂度为 \(\Theta(n\log^2n)\)。
Code
#include <iostream>
#include <cstdio>
#include <vector>
#define int long long
const int N=2e5+10;
const int inf=1e18;
using namespace std;
inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>'9' || ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}
inline void write(int x) {
cout<<x; putchar('\n');
}
int n, m, cnt_edge;
struct edge {
int next, to, val;
}e[N<<1];
int head[N];
struct node {
int opt, x, y;
}a[N];
vector <int> q[N];
int ans[N];
inline void add_edge(int u,int v,int w) {
e[++cnt_edge].to=v;
e[cnt_edge].val=w;
e[cnt_edge].next=head[u];
head[u]=cnt_edge;
}
int dep[N];
struct Heavy {
int size[N], son[N], fa[N], top[N];
void dfs1(int now,int father) {
fa[now]=father, size[now]=1;
int maxson=-1;
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v==father) continue;
dep[v]=dep[now]+e[i].val;
dfs1(v, now);
size[now]+=size[v];
if(maxson < size[v]) {
maxson=size[v], son[now]=v;
}
}
}
void dfs2(int now,int topf) {
top[now]=topf;
if(son[now]) dfs2(son[now], topf);
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v==fa[now] || v==son[now]) continue;
dfs2(v, v);
}
}
void prework() {
dfs1(1, 0);
dfs2(1, 1);
}
inline 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;
}
inline int Dis(int x,int y) {
int lca=LCA(x, y);
int res=dep[x]+dep[y]-2*dep[lca];
return res;
}
}H;
struct Part {
int root, sum_root;
int maxp[N], size[N], fa[N], mi[N], bin[N];
bool vis[N];
void get_root(int now,int fa) {
maxp[now]=0, size[now]=1;
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(v==fa || vis[v]) continue;
get_root(v, now);
size[now]+=size[v];
maxp[now]=max(maxp[now], size[v]);
}
maxp[now]=max(maxp[now], sum_root-maxp[now]);
if(maxp[now] < maxp[root]) root=now;
}
void build(int now) {
vis[now]=1;
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(vis[v]) continue;
sum_root=size[v], root=0;
get_root(v, now);
fa[root]=now;
build(root);
}
}
void build() {
maxp[0]=n+1, sum_root=n;
root=0, get_root(1, 0);
build(root);
}
inline void up_date(int x,int y,int rt) {
int len=H.Dis(x, rt);
for(int i=y;i;i=fa[i]) {
int dis=H.Dis(i, y)+len;
mi[i]=min(mi[i], dis);
}
}
inline int query(int x,int y,int rt) {
int len=H.Dis(x, rt), res=inf;
for(int i=y;i;i=fa[i]) {
int val=mi[i]+H.Dis(i, y);
res=min(res, val+len);
}
return res;
}
void clear(int now) {
for(int i=now;i;i=fa[i]) mi[i]=inf;
}
void calc(int now) {
int tot=0;
for(auto &p : q[now]) {
if(a[p].opt==1) {
bin[++tot]=a[p].y;
up_date(a[p].x, a[p].y, now);
}
if(a[p].opt==2) {
int res=query(a[p].x, a[p].y, now);
ans[p]=min(ans[p], res);
}
}
for(int i=1;i<=tot;i++) clear(bin[i]);
}
void solve(int now) {
vis[now]=1;
calc(now);
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(vis[v]) continue;
root=0, sum_root=size[v];
get_root(v, now);
solve(root);
}
}
void work() {
for(int i=1;i<=n;i++) vis[i]=0;
sum_root=n, maxp[0]=n+1;
root=0, get_root(1, 0);
solve(root);
}
}P;
void prework() {
H.prework();
P.build();
int lim=max(m, n);
for(int i=1;i<=lim;i++) {
ans[i]=P.mi[i]=inf;
}
}
void insert(int id) {
int tmp=a[id].x;
while(tmp) {
q[tmp].push_back(id);
tmp=P.fa[tmp];
}
}
signed main() {
n=read(), m=read();
for(int i=1;i<n;i++) {
int u=read(), v=read(), w=read();
add_edge(u, v, w);
add_edge(v, u, w);
}
prework();
for(int i=1;i<=m;i++) {
a[i]=(node){read(), read(), read()};
insert(i);
}
P.work();
for(int i=1;i<=m;i++) {
if(a[i].opt==1) continue;
if(ans[i]>=inf) write(-1);
else write(ans[i]);
}
return 0;
}
T4
不会,咕了。
NOIP2023模拟8联测29
T1
发现加入一个数一定不优,删去一个数一定不劣,那么考虑维护一个双指针,在移动左指针时移动右指针,发现右指针一定单调向右移。
维护一个线段树,维护值域上的最长连续段,指针边移边修改即可。
复杂度为 \(\Theta(n\log n)\)。
Code
#include <iostream>
#include <cstdio>
#define int long long
const int N=2e5+10;
using namespace std;
inline int read() {
int f=1, x=0;
char ch=getchar();
while(ch>'9' || ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return f*x;
}
inline void write(int x) {
cout<<x; putchar('\n');
}
int n, k;
int a[N];
struct Tree {
int sum, lc, rc, num;
}t[N<<2];
struct Seg_Tree {
inline void push_up(int k,int l,int r) {
int mid=(l+r)>>1;
int len=t[k<<1].rc+t[k<<1|1].lc;
t[k].sum=max(len, max(t[k<<1].sum, t[k<<1|1].sum));
if(t[k<<1].lc==(mid-l+1)) t[k].lc=t[k<<1].lc+t[k<<1|1].lc;
else t[k].lc=t[k<<1].lc;
if(t[k<<1|1].rc==(r-mid)) t[k].rc=t[k<<1|1].rc+t[k<<1].rc;
else t[k].rc=t[k<<1|1].rc;
}
void change(int k,int l,int r,int pos,int val) {
if(l==r) {
t[k].num+=val;
if(t[k].num) t[k].sum=t[k].lc=t[k].rc=1;
else t[k].sum=t[k].lc=t[k].rc=0;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) change(k<<1, l, mid, pos, val);
else change(k<<1|1, mid+1, r, pos, val);
push_up(k, l, r);
}
}T;
signed main() {
n=read(), k=read();
for(int i=1;i<=n;i++) a[i]=read();
if(k==0) {
write(0); return 0;
}
int r=0, ans=0;
for(int i=1;i<=n;i++) {
while(t[1].sum<=k && r<=n) {
r++;
if(r>n) break;
T.change(1, 1, n ,a[r], 1);
}
ans+=(r-i);
T.change(1, 1, n, a[i], -1);
}
write(ans);
return 0;
}
T2
没意思,咕了。
T3
不会,咕了。
T4
没看懂题,咕了。