前言
又下飞分了…
来补一下题.
1406A - Subset Mex
把一些数分成两个可重集合 A , B A,B A,B.
最大化两个集合的 m e x ( ) mex() mex()之和.
贪心.
对于只出现一次的数,我们如果这个数在
A
A
A的
m
e
x
mex
mex值中能发挥作用,那么就不会丢到
B
B
B中.
因为一个的贡献为1,另一个的贡献
≤
1
\le 1
≤1.
int n,vis[110];
int main() {
int _;qr(_); while(_--) {
qr(n); memset(vis,0,sizeof vis);
for(int i=1,x;i<=n;i++) qr(x),vis[x]++;
int ans=0,x=0;
while(vis[x]) vis[x++]--;
ans+=x; x=0;
while(vis[x]) vis[x++]--;
ans+=x; pr2(ans);
}
return 0;
}
1406B - Maximum Product
选5个不同的数,使他们的乘积最大.
直接
d
p
dp
dp,因为有负数,所以记录一个最大值,一个最小值.
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个数选择
j
j
j个的最大值.
g
g
g为最小值.
int n,a[N];
ll f[N][7],g[N][7],ans;
int main() {
int _;qr(_); while(_--) {
qr(n);
memset(f,-63,sizeof(f[0])*(n+1));
memset(g,63,sizeof(g[0])*(n+1));
ans=f[0][0];
for(int i=1;i<=n;i++) {
ll x;qr(x);
f[i][1]=max(f[i-1][1],(ll)x);
g[i][1]=min(g[i-1][1],(ll)x);
for(int j=2;j<=5&&j<=i;j++) {
f[i][j]=max(f[i-1][j],max(f[i-1][j-1]*x,g[i-1][j-1]*x));
g[i][j]=min(g[i-1][j],min(f[i-1][j-1]*x,g[i-1][j-1]*x));
}
}
pr2(f[n][5]);
}
return 0;
}
1406C - Link Cut Centroids
给你一棵树,你需要删一条边加一条边使得重心唯一.
首先,一个重要条件是一棵树的重心至多为2且一定相邻.
那么我们只用考虑两个重心的情况:
我们设两重心为
x
,
y
x,y
x,y.那么我们把
x
x
x的儿子接一个到
y
y
y上面即可.
正确性证明:
y
y
y的最大子树
<
n
/
2
<n/2
<n/2.其他点如果为重心,那么最大子树
≥
n
−
m
a
x
s
i
z
e
(
s
o
n
(
y
)
)
≥
n
/
2
\ge n-maxsize(son(y))\ge n/2
≥n−maxsize(son(y))≥n/2.
int T,n,f[N],sz[N],fa[N],u,v;
struct edge{int y,next; } a[N*2]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len; }
void dfs(int x) {
sz[x]=f[x]=1;
for(int k=last[x],y;k;k=a[k].next)
if((y=a[k].y)^fa[x]) {
fa[y]=x; dfs(y);
sz[x] += sz[y];
f[x]=max(f[x],sz[y]);
}
f[x]=max(f[x],n-sz[x]);
}
int main() {
qr(T); while(T--) {
qr(n); len=1;
for(int i=1;i<=n;i++) last[i]=0;
for(int i=1,x,y;i<n;i++) qr(x),qr(y),ins(x,y),ins(y,x);
fa[1]=0; dfs(1); u=1; v=0;
for(int i=2;i<=n;i++)
if(f[u]>f[i]) u=i,v=0;
else if(f[u] == f[i]) v=i;
if(!v) {
pr1(2); pr2(fa[2]);
pr1(2); pr2(fa[2]);
}
else {
fa[u]=0; dfs(u);
for(int k=last[u];k;k=a[k].next)
if(a[k].y^v) {
int x=a[k].y;
pr1(x); pr2(u);
pr1(x); pr2(v);
break;
}
}
}
return 0;
}
1406D - Three Sequences
给定 a a a,求出一个非降序列 b b b和一个非升序列 c c c.
满足 a i = b i + c i a_i=b_i+c_i ai=bi+ci.最小化 max ( b i , c i ) \max(b_i,c_i) max(bi,ci).
我们差分一下.
先令
b
1
=
a
1
,
c
1
=
0
b_1=a_1,c_1=0
b1=a1,c1=0.
然后上升就改
b
b
b,否则改
c
c
c.
我们统计一下
max
(
d
i
,
0
)
\max(d_i,0)
max(di,0)即为
c
c
c.
然后取一下
b
1
,
c
n
b1,c_n
b1,cn中位数即可.
#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pb push_back
#define IT iterator
#define vi vector<int>
#define TP template<class o>
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=1e5+10,size=1<<20,mod=(int)1e9+7,inf=2e9;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar('\n');
}
int n,m;
ll d[N],ans;
void A() {
ans += d[1];
if(ans>=0) pr2((ans+1)/2);
else pr2(ans/2);
ans -= d[1];
}
int main() {
qr(n);
for(int i=1;i<=n;i++) qr(d[i]);
for(int i=n;i>1;i--) ans += max(0LL,d[i] -= d[i-1]);
A();qr(m); while(m--) {
int l,r,x; qr(l); qr(r); qr(x);
if(l>1) ans -= max(d[l],0LL);
ans -= max(d[r+1],0LL);
d[l] += x;
if(l>1) ans += max(d[l],0LL);
if(r<n) ans += max(d[r+1] -= x,0LL);
A();
}
return 0;
}
1406E - Deleting Numbers
比赛的时候,看 C , D C,D C,D不太会,就跑来淦 E E E,结果最后还没搞出来.
我觉得败笔在于不太会调交互题,只会提交调试法.
其实只要询问操作的时候,在函数内自行交互即可.(就是把答案先读入,然后交互围绕答案进行).
题意:给定 n n n,初始集合为 [ 1 , n ] [1,n] [1,n],你需要猜数.有以下3种操作:
- x x x. 查询集合 x x x的倍数个数.
- x x x. 执行完 1 1 1后,删除所有倍数.但是要猜的数并不会删除.
- x x x.找到答案 x x x.
n ≤ 1 e 5 n\le 1e5 n≤1e5,交互次数为 10000 10000 10000.
我们先猜一下
10000
10000
10000的实际意义.
由于
n
n
n以内的质数个数大约
9500
9500
9500.
所以我们要每个质数均摊一次询问.
答案有3种情况: 1 1 1,质数,合数.
先处理最简单的合数.
显然最小质因子至多为
n
\sqrt n
n.
所以我们先查询
n
\sqrt n
n内的所有质因子的指数.
初始令
r
e
s
=
1
res=1
res=1.(表示答案)
查询细节:
- B ( p ) B(p) B(p).
- 再来一次 B ( p ) B(p) B(p),如果 B ( p ) = 1 B(p)=1 B(p)=1,说明 p ∣ a n s → r e s = r e s ⋅ p p|ans\rightarrow res=res\cdot p p∣ans→res=res⋅p.
- 尝试 B ( p 2 ) B(p^2) B(p2),如果 B ( p 2 ) = 1 B(p^2)=1 B(p2)=1, r e s = r e s ⋅ p res=res\cdot p res=res⋅p.
- 尝试 B ( p 3 ) B(p^3) B(p3)…
这样每个质因子都要查2次,然后就是加上
a
n
s
ans
ans的质因子个数
log
n
\log n
logn.
因为这个质因子
p
≤
n
p\le \sqrt n
p≤n,所以只有几十个.
两者对询问次数的影响很小.
然后
a
n
s
ans
ans可能还有一个
>
n
>\sqrt n
>n的质因子.
所以我们遍历所有
>
n
>\sqrt n
>n的质因子
p
p
p.
如果
A
(
p
)
>
1
A(p)>1
A(p)>1,那么就一定有这个质因子.
剩下的情况就是
>
n
>\sqrt n
>n的质数和
1
1
1.
我们只要排除质数的情况即可.
找质数我们考虑分治.
对于
[
l
,
r
]
[l,r]
[l,r]内的质数我们先对一半的质数
B
(
p
)
(
p
∈
[
l
,
m
i
d
]
)
)
B(p)(p\in[l,mid]))
B(p)(p∈[l,mid])).
然后
A
(
1
)
A(1)
A(1)一下,看看和预估的答案是否一致.
如果不一致,那么答案就在
[
l
,
m
i
d
]
[l,mid]
[l,mid]中,再扫一遍即可.
否则递归处理
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r].
可以发现这个询问次数是质因子个数.
代码中有#define op false
,这个是用来调试的.
#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
#define fi first
#define se second
#define pb push_back
#define IT iterator
#define vi vector<int>
#define vl vector<ll>
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=1e5+10,size=1<<20,mod=998244353,inf=2e9;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,prime[N],tot,a[N],cnt; bool v[N];
ll ans,res;
#define op false
bool vis[N];
int A(int x) {
if(op) {
int s=0;
for(register int i=x;i<=n;i+=x) s += vis[i];
return s;
}
printf("A %d\n",x); fflush(stdout);
qr(x); return x;
}
int B(int x) {
if(op) {
int s=0;
for(register int i=x;i<=n;i+=x) s += vis[i],vis[i]=0;
vis[res]=1;
return s;
}
printf("B %d\n",x); fflush(stdout);
qr(x); return x;
}
void C() {printf("C %lld\n",ans); exit(0);}
int last;
void solve(int l,int r) {
if(l > r) return ;
if(l == r) {
B(a[l]);
if(B(a[l])) ans *= a[l];//因子至多有一个.
return ;
}
int mid=(l+r)/2;
for(int i=l;i<=mid;i++) B(a[i]);
last -= mid-l+1;
if(A(1)^last) {
for(int i=l;i<=mid;i++)
if(B(a[i])) ans *= a[i],C();
}
solve(mid+1,r);
}
int main() {
qr(n); int m=sqrt(n)+1; ans=1;
if(op) {
qr(res);
for(int i=1;i<=n;i++) vis[i]=1;
}
for(int i=1;i<=n;i++) v[i]=1;
for(int i=2;i<=n;i++) if(v[i]) {
prime[++tot]=i;
if(i>m) a[++cnt]=i;
else {
B(i);
if(B(i)) {
ans *= i;
for(ll x=i*i;ans*i<=n;x*=i)
if(B(x)) ans *= i;
else break;
}
if(ans*i>n) C();
}
for(ll j=(ll)i*i;j<=n;j+=i) v[j]=0;
}
if(ans>1) {
for(int i=1;i<=cnt;i++)
if(A(a[i])>1) ans *= a[i],C();
}
else last=A(1),solve(1,cnt);
C();
}