CFR 杂题
CF1986D Mathematical Problem
题意:
在长度为 \(n\) 的数字串中插入 \(n-2\) 个运算符(\(+\) 或 \(\times\))使得该串的运算结果最小,多组询问。\(T\le 10^4,n\le 20\)。
题解:
手模一下样例可以发现,对于 \(n\ge 4\) 的串,如果出现了 \(0\),则总能插入 \(n-2\) 个乘号,使得该式的最小值为 \(0\);对于 \(n=3\) 的串,可以直接暴力判断插入的符号;对于 \(n=2\) 的串,不需要任何操作,只需要去除前导零输出原数即可。
先理解一个比较显然的东西:对于两个相邻位置上的两个数 \(a,b\in[1,9]\),当 \(a,b\neq 1\) 时 \(ab>a+b\),否则 \(ab\le a+b\)。
由于只有 \(n-2\) 个操作符,而可以放操作符的位置有 \(n-1\) 个,所以对于没有出现 \(0\) 且 \(n\ge 4\) 的串,我们就可以先暴力枚举之间没有符号的位置,把这两位合并成一个两位数,然后循环统计答案:
- 当前位置的值 \(>1\),统计答案,加上该位置的值;
- 当前位置的值 \(=1\),不进行任何操作(相当于把 \(1\) 乘到另一个数上)。
可以结合样例 \(7\) 理解一下:
S=23311
i=1
23 3 1 1 --- ans=23+3*1*1=26
i=2
2 33 1 1 --- ans=2+33*1*1=35
i=3
2 3 31 1 --- ans=2+3+31*1=36
i=4
2 3 3 11 --- ans=2+3+3+11=19
minans=19
总时间复杂度 \(O(Tn^2)\)。代码实现可能略有不同。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,a[33];
char s[33];
signed main(){
cin>>T;
while(T--){
cin>>n;
cin>>s;
if(n==2){
cout<<(s[0]-'0')*10+s[1]-'0'<<'\n';
continue;
}
if(n==3){
int s1=s[0]-'0',s2=s[1]-'0',s3=s[2]-'0';
cout<<min(min(s1+s2*10+s3,s1*(s2*10+s3)),min(s1*10+s2+s3,(s1*10+s2)*s3))<<'\n';
continue;
}
int f0=0;
for(int i=0;s[i];i++){
if(s[i]=='0') f0=1;
}
if(f0){
cout<<"0\n";
continue;
}
int ans=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<n;i++){
a[i]=(s[i-1]-'0')*10+s[i]-'0';
for(int j=1;j<i;j++){
a[j]=s[j-1]-'0';
}
for(int j=i+1;j<n;j++){
a[j]=s[j]-'0';
}
int cnt=0;
for(int j=1;j<n;j++){
while(a[j]==1&&j<n) j++;
if(j<n) cnt+=a[j];
/* 这里可以改成
if(a[j]!=1) cnt+=a[j];
*/
}
ans=min(ans,cnt);
}
cout<<ans<<'\n';
}
return 0-0;
}
CF1975D Paint the Tree
题意:
给你一棵白色的树,只有 \(a\) 为红色,\(b\) 为蓝色,树上有两个棋子 \(P_A,P_B\) 分别在节点 \(a,b\) 上,在每一单位时间内,进行以下操作:
- 将 \(P_A\) 移动到相邻节点 \(a'\),若 \(a'\) 为白色,则将 \(a'\) 变为红色;
- 将 \(P_B\) 移动到相邻节点 \(b'\),若 \(b'\) 为红色,则将 \(b'\) 变为蓝色。
求将整棵树染成蓝色的最短时间。\(n\le 2\times 10^5\)。
题解:
首先我们需要知道一点:如果 \(P_B\) 走的点是白色的,那么它走的就没意义。
所以我们首先要最小化 \(P_B\) 没意义的时间。很简单,就是尽快让 \(P_A,P_B\) 相遇,让它们一直走 \((a,b)\) 的最短路径直到互相经过就停下。
从这一刻开始,\(P_B\) 走的就有意义了。而且,\(P_B\) 只要跟着 \(P_A\) 走,就一定是不劣的,因为 \(P_B\) 每一步走的都是红点。
接下来就是用最短时间遍历整棵树,除了从出发点到终点的路径,每条边都必须被经过两次。这其实和 ABC361E 的思想很像了,当然本题规定了出发点,就直接以出发点为根 DFS 出最深度最大的点 \(x\),让 \(P_B\) 的终点为 \(x\),即可保证用时最小。
设 \(P_A,P_B\) 相遇的时间为 \(t\),\(x\) 的深度为 \(d\),则答案为 \(t+2(n-1)-d\),时间复杂度 \(O(n)\)。
小技巧:设 \(a\to b\) 的最短路径长度为 \(l\),记录其路径为 \(f(f_0=a,f_l=b)\),则相遇后,\(P_A=f_{\left\lfloor\frac{l+1}{2}\right\rfloor},P_B=f_{\left\lfloor\frac{l}{2}\right\rfloor},t=\left\lfloor\frac{l+1}{2}\right\rfloor\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
int n,pa,pb,ans,T;
vector<int>e[maxn];
int dep[maxn],stk[maxn],top,f[maxn],len;
void dfs(int u,int fa){
if(u==pb){
for(int i=1;i<=top;i++) f[i]=stk[i];
len=top;
return;
}
for(int v:e[u]){
if(v!=fa){
stk[++top]=v;
dfs(v,u);
top--;
}
}
}
void dfs1(int u,int fa,int depth){
dep[u]=depth;
for(int v:e[u]){
if(v!=fa){
dfs1(v,u,depth+1);
}
}
}
void solve(){
ans=0;
cin>>n;
cin>>pa>>pb;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs(f[0]=pa,0);
if(len&1){
ans+=len/2+1;
pb=f[len/2];
pa=f[len/2]+1;
}else{
ans+=len/2;
pa=pb=f[len/2];
}
dfs1(pb,0,0);
int mxdep=0;
for(int i=1;i<=n;i++){
mxdep=max(mxdep,dep[i]);
e[i].clear();
}
top=0;
cout<<ans+2*(n-1)-mxdep<<'\n';
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0;
}
CF1982D Beauty of the mountains
题意:
给你一个 \(n\times m\) 的矩阵,每个格子有高度 \(a_{i,j}\),每个格子都属于有雪或无雪两种状态,现在你可以进行无限次操作:
- 将 \(k\times k\) 的子矩阵高度加上 \(c(c\in \mathbf N)\)。
询问是否能使整个矩阵中,有雪的格子的高度和等于无雪的格子的高度和。
\(n,m\le 500,\sum nm\le 2.5\times 10^5\)。
题解:
不妨令有/无雪的格子的高度对答案的贡献为正/负数,当前整个矩阵的答案和 \(H\),我们就需要用操作提供贡献 \(w\) 使得 \(w+H=0\)。
对于子矩阵加上 \(c\),设有/无雪的格子数量为 \(x,y\),则加上的贡献为 \(x\times c,y\times c\),对整个矩阵的贡献为 \((x-y)\times c\)。
我们就前缀和求出每个 \(k\times k\) 子矩阵中 \((x-y)\) 的值,记录在数组 \(b\) 中,则问题就转化为判断 \((n-k+1)^2\) 元一次不定方程 \(\left(\sum\limits_{i=1}^{n-k+1}\sum\limits_{j=1}^{m-k+1}c_{i,j}b_{i,j}\right)+H=0\) 是否有整数解。由裴蜀定理即可解决。时间复杂度 \(O(nm\log V)\),\(V\) 为值域。
\(n\) 元一次不定方程 \(\sum\limits_{i=1}^n c_ix_i=k\) 有整数解的充分必要条件是 \(k=\gcd\limits_{i=1}^n(c_i)\)。由乘法分配律可知,本题中只要 \(-H\) 为 \(\gcd\) 的整数倍即可。注意判断 \(\gcd\) 为 \(0\) 的情况。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=503;
int T,n,m,k,a[maxn][maxn],pre[maxn][maxn],sum,g[maxn*maxn],cnt;
void solve(){
sum=cnt=0;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char ch;
cin>>ch;
if(ch=='0'){
a[i][j]=-a[i][j];
}
sum+=a[i][j];
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+(ch=='1'?1:-1);
}
}
for(int i=1;i<=n-k+1;i++){
for(int j=1;j<=m-k+1;j++){
g[++cnt]=pre[i+k-1][j+k-1]-pre[i-1][j+k-1]-pre[i+k-1][j-1]+pre[i-1][j-1];
}
}
int gcd=g[1];
for(int i=1;!g[i]&&i<=cnt;i++) gcd=g[i];
for(int i=1;i<=cnt;i++){
if(g[i]==0) continue;
gcd=__gcd(gcd,g[i]);
}
if(!sum){
cout<<"YES\n";
return;
}
if(!gcd){
cout<<"NO\n";
return;
}
if(sum%gcd==0) cout<<"YES\n";
else cout<<"NO\n";
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0l;
}
CF1990D Gird Puzzle
题意:
有一个 \(n \times n\) 的网格,其中第 \(i\) 行的前 \(a_i\) 个格子为黑色(如果 \(a_i=0\) 说明这一行没有黑色格子),其他格子为白色。
在一次操作中,你可以从下面任选一种:
- 将一个 \(2 \times 2\) 的区域全部染成白色。
- 将一整行全部染成白色。
你的目标是将整个网格全部染成白色,并且保证总操作次数最少。
题解:
\(n\le 2\times 10^5\),显然暴力不可做。
尝试寻找一些性质,可以发现,当 \(a_i>4\) 时,不可能使用操作 \(1\),因为会更劣;对于 \(a_i\le 4\) 的情况,可能用操作 \(1\) 或操作 \(2\),DP 或贪心即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
int T,n,a[maxn],f[maxn],g[2];
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
g[0]=g[1]=n;
for(int i=1;i<=n;i++){
f[i]=f[i-1]+1;
if(!a[i]) f[i]=f[i-1];
if(a[i]<=2){
f[i]=min(f[i],i+g[i&1^1]);
g[i&1]=min(g[i&1],f[i-1]-i);
}
if(a[i]>4) g[0]=g[1]=n;
}
cout<<f[n]<<'\n';
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0l;
}
CF1951D Buying Jewels
题意:
Alice 有 \(n\) 枚硬币,她要用这些硬币去商店恰好消费 \(k\) 件商品。
商店有不超过 \(60\) 个摊位,每个摊位都只有一种商品,每种商品都有无数个。
Alice 会从第一个摊位一直消费到最后一个摊位,直至不够硬币消费为止。Alice 在当前摊位消费时,会尽可能多地消费。
构造一种方案使得 Alice 恰好消费 \(k\) 件商品。
题解:
若 \(k>n\) 显然无解。
若 \(k=n\),显然设置一个 11 即可。
若 \(n/2<k<n\) 则除了 \(n=2k+1\) 的情况外均无解。
考虑一种构造:
2
n-k+1 1
AC。灵光乍现()
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T,n,k;
void solve(){
cin>>n>>k;
if(k==1){
cout<<"YES\n1\n"<<n<<'\n';
return;
}
if(n==k){
cout<<"YES\n1\n1\n";
return;
}
if(n-(k-1)<k){
cout<<"NO\n";
return;
}
cout<<"YES\n2\n";
cout<<n-(k-1)<<' '<<1<<'\n';
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0l;
}
CF514E Darth Vader and Tree
军训被硬控 7 天的题……
给一棵无限高的树,每个节点有 \(n\) 个子节点,\((u,son_i)\) 的长度为 \(d_i\),求从根出发,在总距离 \(\le x\) 的前提下,可以到达的点的个数。
\(n\le 10^5,x\le 10^9,V=d_{\max}\le 100\)
设 \(f_i\) 表示距离根长度为 \(i\) 的点的数量,则显然有转移 \(f_i=\sum\limits_{j=1}^n f_{i-d_j}\),初始 \(f_0=1\),答案为 \(\sum\limits_{i=0}^x f_i\)。时间复杂度 \(O(nx)\),无法通过。
我们先修改一下转移式子,设 \(cnt_{d_i}\) 为 \(d_i\) 出现次数,则原式为 \(f_i=\sum\limits_{j=1}^{V} cnt_jf_{i-j}\) 由于 \(V\) 只有 \(100\),所以时间复杂度 \(O(Vx)\),还是无法通过。
看 \(x\) 的范围就很矩阵的样子。由于 \(f_i\) 至多从 \(f_{i-V}\) 转移过来,所以只要开个 \(V+1\)(有一层记录前缀和 \(s_i\))的矩阵就可以优化 \(x\) 了,每次只更新 \(f_i\) 和 \(s_i\),其余直接继承即可。
时间复杂度 \(O(V^3\log x)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int maxd=100;
const int maxn=2e5+3;
const int maxk=1e9+7;
int n,x,d[maxn];
struct matrix{
int a[maxd+3][maxd+3];
matrix(){memset(a,0,sizeof a);}
matrix friend operator*(const matrix a,const matrix b){
matrix c;
for(int i=0;i<=maxd;i++)
for(int j=0;j<=maxd;j++)
for(int k=0;k<=maxd;k++)
c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
return c;
}
};
matrix qpow(matrix t,matrix a,int b){
matrix ret=t;
for(;b;a=a*a,b>>=1ll) if(b&1) ret=ret*a;
return ret;
}
int cnt[maxd+3];
signed main(){
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>d[i];
cnt[d[i]]++;
}
matrix a,b;
a.a[0][0]=b.a[0][0]=b.a[0][1]=1;
for(int i=1;i<=100;i++) a.a[i][0]=a.a[i][1]=cnt[i];
for(int i=2;i<=100;i++) a.a[i-1][i]=1;
cout<<qpow(b,a,x).a[0][0];
return 0;
}
CF253D Table with Letters - 2
给你一个 \(n\times m\) 的矩阵,求其子矩形满足四个角字符相等且 a
的数量不超过 \(k\) 的个数。
\(n,m\le 400,k\le nm\)
子矩形中 a
的个数显然可以用前缀和处理。
枚举左上端点 \((i,j)\),再枚举矩形的下边界 \((l,\cdot )\),当 \(i,l\) 固定时,考虑到随着 \(j\) 的增大,a
的个数单增,于是可以设指针 \(o\) 从左往右扫,即可满足 \(k\) 的限制。
再考虑处理字符相等,当 \(i,l\) 固定时,一个矩形可以拆成左右两条边界颜色相等,记一个桶 \(t[c]\) 表示当前颜色 \(c\) 代表的边界数,\(j,o\) 向右扫时更新 \(t[c]\) 即可,时间复杂度 \(O(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=403;
int pre[maxn][maxn],ton[26];
int n,m,k;
char a[maxn][maxn];
int p(int x1,int y1,int x2,int y2){
return pre[x2][y2]-pre[x1-1][y2]-pre[x2][y1-1]+pre[x1-1][y1-1];
}
signed main(){
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+(a[i][j]=='a');
}
}
int cnt=0;
for(int i=1;i<n;i++){
for(int l=i+1;l<=n;l++){
for(int j=0;j<26;j++) ton[j]=0;
for(int j=1,o=1;j<m;j++){
if(a[i][j]!=a[l][j]) continue;
while(o<=m&&p(i,j,l,o)<=k){
if(a[i][o]==a[l][o])
ton[a[i][o]-'a']++;
o++;
}
ton[a[i][j]-'a']--;
if(ton[a[i][j]-'a']>0)
cnt+=ton[a[i][j]-'a'];
}
}
}
cout<<cnt<<'\n';
return 0;
}
CF1288E Messenger Simulator
给你一个长为 \(n\) 的链表,\(m\) 次操作,第 \(i\) 次将 \(a_i\) 放至链头,求对于每个 \(a_i\),其最小/大下标。
\(n,m\le 3\times 10^5\)
最小下标很好求,如果 \(a_i\) 被操作过就为 \(1\) 否则为 \(i\)。
最大下标,设 \(p_{i}\) 表示当前 \(a_i\) 的位置(在这 \(n\) 个点之前加 \(m\) 个空点方便操作,所以初始为 \(p_i=m+i\))。考虑一个 \(a_i\) 的下标为其前面的数的个数加一,可以用树状数组维护。删掉原来的点接到开头,相当于在位置 \(p_i\) 的树状数组上减一,在 \(i\) 上加一;查询即前缀和。时间复杂度 \(O((n+m)\log(n+m))\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+3;
int n,m;
int a[maxn],f[maxn],g[maxn],tr[maxn],now[maxn];
int lowbit(int x){return x&-x;}
void add(int x,int c){
for(;x<=n+m;x+=lowbit(x)) tr[x]+=c;
}
int q(int x){
int res=0;
for(;x;x-=lowbit(x)) res+=tr[x];
return res;
}
signed main(){
cin>>n>>m;
for(int i=m+1;i<=m+n;i++) a[i]=i-m,now[i-m]=i,add(i,1);
for(int i=1;i<=n;i++) f[i]=g[i]=i;
for(int i=m;i;i--){
cin>>a[i];
g[a[i]]=max(g[a[i]],q(now[a[i]]));
add(now[a[i]],-1);
add(i,1);
now[a[i]]=i;
f[a[i]]=1;
}
for(int i=1;i<=n;i++){
g[i]=max(g[i],q(now[i]));
cout<<f[i]<<' '<<g[i]<<'\n';
}
return 0;
}
CF438D The Child and Sequence
给你一个序列 \(a\),维护以下操作:
- 求 \(\sum\limits_{i=l}^r a_i\);
- \(\forall i\in [l,r]\),\(a_i\leftarrow a_i \bmod x\);
- 单点修改 \(a_i\leftarrow x\)。
\(n,m\le 10^5,x\le 10^9\)
1,3 操作很容易搞。考虑取模有性质:\((a\bmod x)\bmod y\le \frac{x}{2}(a>x,a\bmod x>y)\)。
证明:
- 若 \(y\ge \frac{x-1}{2}\),则原式 \(\le x-y\le \frac{x}{2}\);
- 若 \(y<\frac{x-1}{2}\),显然成立。
所以有效的取模次数是 \(O(\log a)\) 级别的,把不满足上述条件的 skip 掉,其余暴力修改即可。时间复杂度 \(O(n\log n\log x)\)。
代码使用 Codeforces AtCoder Better!模版 3min 完成。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls (u<<1)
#define rs (u<<1|1)
using namespace std;
const int maxn=1e5+3;
int n,m;
int a[maxn];
struct Node
{
int l, r;
int mx;
int sum;
// TODO: 需要维护的信息和懒标记
}tr[maxn * 4];
void pushup(int u)
{
tr[u].mx=max(tr[ls].mx,tr[rs].mx);
tr[u].sum=tr[ls].sum+tr[rs].sum;
// TODO: 利用左右儿子信息维护当前节点的信息
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r,a[l],a[l]};
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void update(int u, int l, int r, int d)
{
if(tr[u].mx<d) return; // 没必要改直接返回
if (tr[u].l==tr[u].r)
{
tr[u].mx=tr[u].sum=tr[u].sum%d;
}
else
{
// pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update(u << 1, l, r, d);
if (r > mid) update(u << 1 | 1, l, r, d);
pushup(u);
}
}void update1(int u, int l, int d)
{
if (tr[u].l==tr[u].r)
{
tr[u].mx=tr[u].sum=d;
}
else
{
// pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) update1(u << 1, l, d);
else update1(u << 1 | 1, l, d);
pushup(u);
}
}
int query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r)
{
return tr[u].sum; // TODO 需要补充返回值
}
else
{
// pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if (l <= mid ) res = query(u << 1, l, r);
if (r > mid) res += query(u << 1 | 1, l, r);
return res;
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
for(int i=1,op,l,r,x;i<=m;i++){
cin>>op;
if(op==1){
cin>>l>>r;
cout<<query(1,l,r)<<'\n';
}else if(op==2){
cin>>l>>r>>x;
update(1,l,r,x);
}else{
cin>>l>>x;
update1(1,l,x);
}
}
return 0;
}
CF920F SUM and REPLACE
给你一个数列,\(m\) 次操作:
- 将区间 \([l,r]\) 中的数变为它们的因数个数;
- 求区间 \([l,r]\) 的和。
\(n,m\le 3\times 10^5,a_i\le 10^6\)
这个题和上面那个 CF438D The Child and Sequence 思路有异曲同工之处。考虑到操作 1,\(x\leftarrow d(x)\),这个操作进行次数为 \(\log \log x\) 级别的,即进行这么多次以后 \(x\) 就变成 \(1/2\) 了,于是我们暴力修改,并记录区间最大值,当区间最大值小于等于 2 时就不再操作。时间复杂度 \(O(m\log n\log \log a_i)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls (now<<1)
#define rs (now<<1|1)
using namespace std;
const int maxn=1e6+3;
int n,m;
int a[maxn],d[maxn],tr[maxn<<2],mx[maxn<<2];
void pushup(int now){// ♂♂♂♂♂♂♂♂♂♂
tr[now]=tr[ls]+tr[rs];
mx[now]=max(mx[ls],mx[rs]);
}
void build(int now,int l,int r){
if(l==r){
mx[now]=tr[now]=a[l];
return;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(now);
}
void modify(int now,int l,int r,int L,int R){
if(mx[now]<=2) return;
if(l==r){
mx[now]=tr[now]=d[tr[now]];
return;
}
int mid=(l+r)>>1;
if(L<=mid) modify(ls,l,mid,L,R);
if(mid+1<=R) modify(rs,mid+1,r,L,R);
pushup(now);
}
int query(int now,int l,int r,int L,int R){
if(L<=l&&r<=R){
return tr[now];
}
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls,l,mid,L,R);
if(mid+1<=R) res+=query(rs,mid+1,r,L,R);
return res;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=1000000;i++){
for(int j=i;j<=1000000;j+=i){
d[j]++;
}
}
build(1,1,n);
for(int i=1,op,x,y;i<=m;i++){
cin>>op>>x>>y;
if(op==1){
modify(1,1,n,x,y);
}else{
cout<<query(1,1,n,x,y)<<'\n';
}
}
return 0;
}
CF1553G Common Divisor Graph
给一个 \(n\) 个点的图,两个点 \(u,v\) 有边当且仅当 \(\gcd(a_u,a_v)>1\)。\(m\) 次询问两个节点 \(u,v\),至少要进行多少次「任选一个 \(i\),新建节点 \(x\),权值 \(a_x=a_i(a_i+1)\)」操作才能让 \(u,v\) 连通。
\(n\le 1.5\times 10^5,m\le 3\times 10^5,2\le a_i\le 10^6\)
由于 \(a_i(a_i+1)\) 一定为偶数,所以至多建 \(a_u(a_u+1),a_v(a_v+1)\) 两个节点即可让 \(u,v\) 连通,即答案至多为 2。
以对每个数质因数分解,塞进对应质因数并查集里,即可维护开始时的连通性。
所以分类讨论:
- 答案为 0:初始时 \(u,v\) 在同一联通块;
- 答案为 1:存在一个 \(i\),\(a_i+1\) 可以与 \(u,v\) 连通,这个可以用一个 set 维护;
- 答案为 2:其他情况。
预处理每个数的质因数即可。时间复杂度 \(O(n\log^2 n+(m+a_i)\log a_i)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
const int maxa=1e6+3;
int n,q;
int a[maxn];
vector<int>v[maxa];
vector<int>p[maxa<<1];
int fa[maxa];
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
set<pair<int,int> >mp;
signed main(){
cin>>n>>q;
for(int i=1;i<=1000001;i++) fa[i]=i;
for(int i=2;i<=1000001;i++){
if(v[i].empty())
for(int j=i;j<=1000001;j+=i)
v[j].emplace_back(i);
}
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j:v[a[i]])
fa[find(a[i])]=find(j);
}
for(int i=1;i<=n;i++){
p[i]=v[a[i]+1];
p[i].emplace_back(a[i]);
int siz=p[i].size();
for(int j=0;j<siz;j++)
for(int k=j+1;k<siz;k++)
mp.insert({find(p[i][j]),find(p[i][k])}),
mp.insert({find(p[i][k]),find(p[i][j])});
}
for(int i=1,x,y;i<=q;i++){
cin>>x>>y;
x=find(a[x]),y=find(a[y]);
if(x==y){
cout<<"0\n";
}else if(mp.count({x,y})||mp.count({y,x})){
cout<<"1\n";
}else{
cout<<"2\n";
}
}
return 0;
}
CF351E Jeff and Permutation
给你一个序列 \(a\),求将某些数变成其相反数后的最小逆序对数。
\(n\le 2000,|a_i|\le 10^5\)
由于可以操作任意次,所以不妨先将数全部取正。
考虑最大的数 \(a_i\),它贡献的逆序对数,显然是独立的,当它取正时,答案为 \(n-i\),取负时贡献为 \(i-1\),将答案取 \(\min\)。对于其他的数,无论它怎么操作,在最大的数的符号确定时一定与最大的数的偏序关系是确定的,所以我们先确定最大值的影响,再依次找次大值,并将答案取 \(\min\) 后累加起来。时间复杂度 \(O(n^2)\)。
还是远古老题,数据范围开得不够。
对于一个数 \(a_i\),它由于受到比它大的数的影响,其答案为 \(\min(i-1-x,n-i-y)\),\(x/y\) 表示 \(i\) 左/右边比它大的数的个数。这样维护一个前缀树状数组,一个后缀的树状数组即可。时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200003;
int n,a[maxn],tr[2][maxn],ans[2][maxn];
int lowbit(int x){return x&-x;}
void add(int x,int c,int t){for(;x<=200000;x+=lowbit(x)) tr[t][x]+=c;}
int get(int x,int t){
int res=0;
for(;x;x-=lowbit(x)) res+=tr[t][x];
return res;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]<0) a[i]=-a[i];
a[i]+=2;
}
for(int i=1;i<=n;i++){
ans[0][i]=get(a[i]-1,0);
add(a[i],1,0);
}
for(int i=n;i;i--){
ans[1][i]=get(a[i]-1,1);
add(a[i],1,1);
}
int anss=0;
for(int i=1;i<=n;i++){
anss+=min(ans[0][i],ans[1][i]);
}
cout<<anss;
return 0;
}
CF2000H Ksyusha and the Loaded Set
给你一个集合 \(S=\{a_1,a_2,\ldots,a_n\}\),\(q\) 次操作:
- 将 \(x\) 加入 \(S\);
- 将 \(x\) 从 \(S\) 中删除;
- 求该集合的 \(k\) - load。
\(k\) - load 定义为最小的 \(d\) 满足 \([d,d+k-1]\cup S=\varnothing\)。
\(T\le 10^4,\sum n,q\le 2\times 10^5,a_i,x,k\le V\le 2\times 10^6\)
建一棵权值线段树,点 \(i\) 的值为 \([a_i\notin S]\),考虑二分 \(d+k-1\),即判断 \([1,d+k-1]\) 中最长的 0 子段长度 \(\ge k\),这个很经典了,就维护区间前缀/后缀/最大 0 子段长度即可。注意一点不能每组数据都重新建树,因为单组数据复杂度不能与值域有关,考虑记一个 set 模拟操作,并最后清空 set 即可。时间复杂度 \(O(V+\sum n\log(\sum n+q)+\sum n\log^2 V)\)。
存在直接线段树二分的 \(O(n\log V)\) 做法。
点击查看代码
#include<bits/stdc++.h>
#define ls (now<<1)
#define rs (now<<1|1)
const int maxn=4e6+7;
const int maxm=2e5+7;
const int N=4e6;
using namespace std;
struct TR{
int mx0,lmx,rmx,len;
}tr[maxn<<2];
void pushup(int now){
tr[now].mx0=max(tr[ls].rmx+tr[rs].lmx,max(tr[ls].mx0,tr[rs].mx0));
if(tr[ls].lmx==tr[ls].len) tr[now].lmx=tr[ls].lmx+tr[rs].lmx;
else tr[now].lmx=tr[ls].lmx;
if(tr[rs].rmx==tr[rs].len) tr[now].rmx=tr[rs].rmx+tr[ls].rmx;
else tr[now].rmx=tr[rs].rmx;
}
void build(int now,int l,int r){
tr[now].len=r-l+1;
if(l==r){
tr[now].mx0=1;
tr[now].lmx=1;
tr[now].rmx=1;
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(now);
}
void modify(int now,int l,int r,int x,int v){
if(l==r){
tr[now].mx0=v;
tr[now].lmx=v;
tr[now].rmx=v;
return;
}
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,v);
else modify(rs,mid+1,r,x,v);
pushup(now);
}
TR query(int now,int l,int r,int R){
if(1<=l&&r<=R) return tr[now];
int mid=(l+r)>>1;
TR resl=query(ls,l,mid,R),resr;
if(mid+1<=R){
resr=query(rs,mid+1,r,R);
resl.mx0=max(resl.rmx+resr.lmx,max(resl.mx0,resr.mx0));
if(resl.lmx==resl.len) resl.lmx=resl.lmx+resr.lmx;
else resl.lmx=resl.lmx;
if(resr.rmx==resr.len) resl.rmx=resr.rmx+resl.rmx;
else resl.rmx=resr.rmx;
}
return resl;
}
int T,n,q;
set<int>intr;
void solve(){
cin>>n;
for(int i=1,a;i<=n;i++){
cin>>a;
intr.insert(a);
modify(1,1,N,a,0);
}
cin>>q;
while(q--){
char op;
int x;
cin>>op>>x;
if(op=='+'){
modify(1,1,N,x,0);
intr.insert(x);
}else if(op=='-'){
modify(1,1,N,x,1);
intr.erase(x);
}else{
int l=1,r=N,ans=N;
while(l<=r){
int mid=(l+r)>>1;
if(query(1,1,N,mid).mx0>=x){
r=mid-1;
ans=mid;
}else{
l=mid+1;
}
}
cout<<ans-x+1<<' ';
}
}
cout<<'\n';
for(int x:intr) modify(1,1,N,x,1);
intr.clear();
}
signed main(){
ios::sync_with_stdio(0);
cin>>T;
build(1,1,N);
while(T--){
solve();
}
return 0;
}
CF1946E Girl Permutation
给你一个排列的前/后缀最大值的所有位置,求满足要求排列数量。
\(n\le 2\times 10^5\)
记前缀最大值的位置为 \(p_1<p_2<\ldots<p_m\),后缀最大值的位置为 \(s_1<s_2<\ldots<s_r\)。
首先容易得知,由于是排列,所以有且仅有一个最大值 \(n\) 在 \(p_m=s_1\) 的位置。其次 \(p_1=1,s_r=n\),长度为 \(1\) 的前后缀的最大值只可能是它自己。以上用来判断无解。
确定了最大值的位置,就以它为界,分开讨论。先考虑左边,是在非最大值数中选了 \(p_m-1\) 个数,即 \(\binom{n-1}{p_m-1}\),然后在这 \(p_m-1\) 个数里,最大值的位置为 \(p_{m-1}\),类似递归的思想,继续分为左右两边区间 \([1,p_{m-1}),(p_{m-1},p_m-1]\),其中左边选掉 \(p_{m-1}-1\) 个,方案为 \(\binom{p_{m}-2}{p_{m-1}-1}\),右边区间内的元素可以任意排列,方案为 \((p_{m}-p_{m-1}-1)!\),然后递归下去即可。右半边同理。时间复杂度 \(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+7;
const int mod=1e9+7;
int T;
int n,m0,m1,p[maxn],s[maxn];
int fac[maxn],ifac[maxn];
int qpow(int a,int b){
int res=1;
for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
return res;
}
int C(int a,int b){
if(a<b) return 0;
return fac[a]*ifac[a-b]%mod*ifac[b]%mod;
}
void solve(){
cin>>n>>m0>>m1;
for(int i=1;i<=m0;i++) cin>>p[i];
for(int i=1;i<=m1;i++) cin>>s[i];
if(p[m0]!=s[1]||p[1]!=1||s[m1]!=n){
cout<<0<<'\n';
return;
}
int ans=C(n-1,p[m0]-1);
for(int i=m0;i>1;i--)
ans=ans*C(p[i]-2,p[i-1]-1)%mod*fac[p[i]-p[i-1]-1]%mod;
for(int i=1;i<m1;i++)
ans=ans*C(n-s[i]-1,n-s[i+1])%mod*fac[s[i+1]-s[i]-1]%mod;
cout<<ans<<'\n';
}
signed main(){
cin>>T;
fac[0]=1;
for(int i=1;i<=200000;i++)
fac[i]=fac[i-1]*i%mod;
ifac[200000]=qpow(fac[200000],mod-2);
for(int i=199999;~i;i--)
ifac[i]=ifac[i+1]*(i+1)%mod;
while(T--){
solve();
}
return 0;
}
CF1996G Penacony
给你一个环和 \(m\) 个点对 \((a_i,b_i)\),将路径 \(a_i\to b_i\) 或 \(b_i\to a_i\) 染色,求最小染色边数。
\(n,m\le 2\times 10^5,a_i<b_i\)
对于每条边,钦定它被所有路径染过色,每次更新染色边数,这样一定是最优的。因为只要有一个点对的路径不经过该边,一定答案劣于经过该边。
考虑优化更新。将环对应到区间上,每次只在某一个节点为 \(a_i/b_i\) 时更新答案,每次对应三段区间加减,线段树搞即可。统计答案考虑容斥,转为求总区间里 0 的个数,充要转化为总区间最小值为 0 时,最小值的数量,这个就可以简单维护了。时间复杂度 \(O(m\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define ls (now<<1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int maxn=2e5+3;
int T;
int n,m,l[maxn],r[maxn];
struct TR{
int mi,cnt;
TR friend operator+(const TR &a,const TR &b){
TR res;
if(a.mi<b.mi) return a;
else if(a.mi>b.mi) return b;
else return {a.mi,a.cnt+b.cnt};
}
}tr[maxn<<2];
int tag[maxn<<2];
void pushup(int now){
tr[now]=tr[ls]+tr[rs];
}
void pushdown(int now){
if(tag[now]){
tag[ls]+=tag[now];
tag[rs]+=tag[now];
tr[ls].mi+=tag[now];
tr[rs].mi+=tag[now];
tag[now]=0;
}
}
void build(int now,int l,int r){
tag[now]=0;
if(l==r){
tr[now]={0,1};
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(now);
}
void modify(int now,int l,int r,int L,int R,int x){
if(L<=l&&r<=R){
tr[now].mi+=x;
tag[now]+=x;
return ;
}
pushdown(now);
if(L<=mid) modify(ls,l,mid,L,R,x);
if(mid+1<=R) modify(rs,mid+1,r,L,R,x);
pushup(now);
}
int query(){
return (tr[1].mi==0)*(tr[1].cnt);
}
int type[maxn];
vector<int>v1[maxn],v2[maxn];
void solve(){
cin>>n>>m;
build(1,1,n);
for(int i=1;i<=m;i++){
cin>>l[i]>>r[i];
v1[l[i]].emplace_back(i);
v2[r[i]].emplace_back(i);
modify(1,1,n,l[i],r[i]-1,1);
}
int ans=n-query();
for(int i=1;i<=n;i++){
for(int j:v1[i]){
modify(1,1,n,l[j],r[j]-1,-1);
if(l[j]>1) modify(1,1,n,1,l[j]-1,1);
modify(1,1,n,r[j],n,1);
}
for(int j:v2[i]){
modify(1,1,n,l[j],r[j]-1,1);
if(l[j]>1) modify(1,1,n,1,l[j]-1,-1);
modify(1,1,n,r[j],n,-1);
}
v1[i].clear();
v2[i].clear();
ans=min(ans,n-query());
}
cout<<ans<<'\n';
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0;
}
人类智慧做法:
考虑从 \(a_i\to b_i\) 变为 \(b_i\to a_i\),非常类似异或。考虑给 \(a_i,b_i\) 赋同一个随机权,然后前缀异或一下,前缀异或和中出现次数最多的数的出现次数即为当前状态下 0 的数量最多的状态。时间复杂度 \(O(n+m)\)。
点击查看 spec. 代码
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
mt19937 Rand(20080217);
int n,m,x,y,i,T,a[200007];
unordered_map<int,int> ma;
signed main()
{
cin>>T;
while(T--){
cin>>n>>m;
for(i=1;i<=m;i++){
cin>>x>>y;
int r=Rand();
a[x]^=r,a[y]^=r;
}
for(i=1;i<=n;i++) a[i]^=a[i-1],ma[a[i]]++;
int ans=0;
for(auto x:ma) ans=max(ans,x.second);
cout<<n-ans<<endl;
for(i=1;i<=n+1;i++) a[i]=0;
ma.clear();
}
}
CF2036F XORificator 3000
求 \([l,r]\) 中满足 \(x\not\equiv k\pmod{2^i}\) 的 \(x\) 的异或和。
\(T\le 10^4,1\le l\le r\le 10^{18},0\le i\le 30,0\le k< 2^i\)
首先由于异或满足 \(a\oplus b\oplus b=a\),所以考虑拆分问题,求出 \([1,l-1]\) 和 \([1,r]\) 的答案异或即可。
再考虑容斥,将区间 \([1,x]\) 的答案转化成 \([1,x]\) 所有数的异或和再异或上区间内满足 \(x\equiv k\pmod{2^i}\) 的数。
对于求 \([1,x]\) 所有数的异或和,这里直接摆结论了,证明可以看 这里,打表也能发现一定规律。
于是 \([1,x]\) 的异或和我们就可以 \(O(1)\) 求了,接下来考虑求满足 \(x\equiv k\pmod{2^i}\) 的数的异或和。
这些数二进制下的后(低位)\(i\) 位都一样,为 \(k\),而高位上都形如 \(0\times 2^i,1\times 2^i,2\times 2^i,...,t\times 2^i\),\(t\) 为 \(x\) 的数量。
由于异或对于每一位的独立性,诶你发现求这一部分的异或和,是不是相当于高位求一遍 \([1,t]\) 的异或和,再异或上 \(t\) 个 \(k\)(由于 \(a\oplus a=0\),所以只用在 \(t\) 为奇数的时候异或一次 \(k\) 即可),时间复杂度 \(O(1)\)。
综上时间复杂度 \(O(T)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int getxor(int x){
if(x%4==0) return x;
if(x%4==1) return 1;
if(x%4==2) return x+1;
return 0;
}
int get(int x,int pos,int k){
if(x==0) return 0;
int t=(x>>pos);
if(x%(1<<pos)>=k) t++;
return getxor(x)^(getxor(t-1)<<pos)^((t&1)*k);
}
void solve(){
int l,r,I,k;
cin>>l>>r>>I>>k;
cout<<(get(r,I,k)^get(l-1,I,k))<<'\n';
}
signed main(){
cin>>T;
while(T--) solve();
return 0;
}
CF2036G Library of Magic
交互题。现有一个 \(1\sim n\) 的整数序列,每个数有 2 个,在该序列里选走 3 个不同的数,求出选走的这些数。可以向交互库发起不超过 150 次询问形如:
xor l r
返回数值在 \([l,r]\) 之间的数的异或和。
\(n\le 10^{18}\)
需要一定智慧的一题。
考虑到异或有性质 \(x\oplus x=0\),所以区间 \([l,r]\) 的数的异或和其实就是区间内被选走的数的异或和。
不妨设选走的这 3 个数分别为 \(a<b<c\)。查一次 \([1,n]\) 的异或和可以得到 \(t=a\oplus b\oplus c\) 的值。据此分类讨论:
- \(t\neq 0\)。这种比较好考虑。对于这个数列的前缀异或和序列 \(p\),具有一定的单调性,即 \(p_1\sim p_{a-1}=0,p_{a}\sim p_{b-1}=a,p_{b}\sim p_{c-1}=a\oplus b,p_{c}\sim p_{n}=t\)。我们不关心具体数值,只关心其是否大于 0,即可二分出 \(a\) 的位置。同理也可根据其后缀异或序列单调性求出 \(c\)。进而算出 \(b=a\oplus c\oplus t\)。
- \(t=0\)。这就不能用上面的方法了,因为 \(p\) 不单调了(形如
00001111000
,1 表示大于 0 的数)。考虑从 \(t=0\) 上获取信息。
即 \(a\oplus b\oplus c=0,a=b\oplus c\)。由于上述问题,对于一个区间 \([l,r]\) 中的断点 \(mid\),可能会出现 \(mid\) 左右的异或和均为 0 的情况。但是事实是一边有 3 个数,另一边没数(不可能出现两边都有数的情况,因为 3 个数都不同,任意 2 个数异或都一定不为 0)。我们需要找到一个 \(mid\) 能让我们在这种情况下也能正确的判断哪一边有数。
当我们取 \(mid=2^x-1,x=\lfloor\log_2 (r-l+1)\rfloor\) 时,所有 \([mid+1,r]\) 的数中任取两个数的异或和均 \(\le mid\)(二进制下位数相等且最高位都为 1,异或完就少了一位,即 \(<2^x\)),即 \(b,c>mid,a\le mid\),不合法;任取 \([l,mid]\) 中的两个数,由于位数不够,异或完还是 \(<2^x\),所以合法,此时我们就把范围缩小到了 \([l,mid]\),循环做这件事直到出现某一边异或和不为 0,即转化为 \(t\neq 0\) 的情况。
综上查询次数最劣为为做两遍二分,\(120\) 次左右。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n;
bool check(int l,int r){
cout<<"xor "<<l<<' '<<r<<'\n';
cout.flush();
int ret;
cin>>ret;
return ret;
}
void solve(){
ck1=ckn=0;
cin>>n;
if(n==3){
cout<<"ans 1 2 3\n";
cout.flush();
return;
}
cout<<"xor 1 "<<n<<'\n';
cout.flush();
int ret;
cin>>ret;
if(ret){
int l=1,r=n,posl=1,posr=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(1,mid)){
r=mid-1;
posl=mid;
}else{
l=mid+1;
}
}
l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid,n)){
l=mid+1;
posr=mid;
}else{
r=mid-1;
}
}
cout<<"ans "<<posl<<' '<<(ret^posl^posr)<<' '<<posr<<'\n';
cout.flush();
}else{
int mx=log2(n),nmid=0;
for(int i=mx;i>=1;i--){
int r=min((1ll<<(i+1))-1,n),l=(1ll<<i);
if(check(l,r)){
nmid=l;
break;
}
}
int l=1,r=nmid,posl=nmid,posr=nmid;
while(l<=r){
int mid=(l+r)>>1;
if(check(1,mid)){
r=mid-1;
posl=mid;
}else{
l=mid+1;
}
}
l=nmid,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid,n)){
l=mid+1;
posr=mid;
}else{
r=mid-1;
}
}
cout<<"ans "<<posl<<' '<<(posl^posr)<<' '<<posr<<'\n';
cout.flush();
}
}
signed main(){
cin>>T;
while(T--){
solve();
}
return 0;
}
CF2033G Sakurako and Chefir
给定一个 \(n\) 个节点以 1 为根的树,\(q\) 次询问,给定 \(u,k\),对于每次询问,你需要操作节点 \(u\) 顺着边进行移动操作,满足:
- 最多向树根(深度减小)方向移动 \(k\) 次;
- 向叶子(深度增加)方向移动任意次。
问操作结束后与原点 \(u\) 的距离最大值。多组询问。
\(\sum n\le 2\times 10^5,k\le n\)
记 \(f(0/1,u)\) 为 \(u\) 的儿子中,子树内深度最/次深的 儿子节点编号,这个预处理下深度就能求了。这样可以做到 \(O(qk)\)。
搞个倍增 \(g(i,u)\) 表示跳 \([0,2^i]\) 次的最大答案。
则有转移 \(g(i,u)=\max(g(i-1,u),g(i-1,fa(i-1,u))+maxdep(fa(u))-dep(u))\)。 \(g(0,u)\) 为不为他自己的 \(f\) 距离 \(u\) 的距离。
就做完了,往上跳的时候累加跳过的距离并取 max 即可。注意不要走包含自己节点的子树(这就是为什么处理 \(f(1,u)\))。时间复杂度 \(O((n+q)\log k)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
vector<int>e[maxn];
int n,q;
int dep[maxn],mxdep[maxn],f[maxn][2],g[19][maxn],fa[19][maxn];
void update(int u,int v){
if(mxdep[f[u][0]]<=mxdep[v]){
f[u][1]=f[u][0];
f[u][0]=v;
return;
}
if(mxdep[f[u][1]]<=mxdep[v]) f[u][1]=v;
}
void dfs(int u,int Fa){
dep[u]=dep[Fa]+1;
mxdep[u]=dep[u];
fa[0][u]=Fa;
for(int v:e[u]) if(v!=Fa)
dfs(v,u),mxdep[u]=max(mxdep[u],mxdep[v]);
}
void dfs1(int u,int Fa){for(int v:e[u]) if(v!=Fa) dfs1(v,u),update(u,v);}
void init(){
for(int i=1;i<=n;i++){
if(f[fa[0][i]][0]==i){
if(!f[fa[0][i]][1])g[0][i]=1;
else g[0][i]=mxdep[f[fa[0][i]][1]]-dep[fa[0][i]]+1;
}else
g[0][i]=mxdep[f[fa[0][i]][0]]-dep[fa[0][i]]+1;
}
for(int i=1;i<=n;i++) f[i][0]=f[i][1]=0;
for(int j=1;j<=17;j++)
for(int i=1;i<=n;i++)
fa[j][i]=fa[j-1][fa[j-1][i]];
for(int j=1;j<=17;j++)
for(int i=1;i<=n;i++)
g[j][i]=max(g[j-1][i],g[j-1][fa[j-1][i]]+dep[i]-dep[fa[j-1][i]]);
}
void solve(){
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
dfs(1,0);
dfs1(1,0);
init();
cin>>q;
for(int i=1,u,k;i<=q;i++){
cin>>u>>k; k=min(k,dep[u]-1);
int res=mxdep[u]-dep[u],now=0;
for(int j=17;~j;j--)
if(k&(1<<j)) res=max(res,g[j][u]+now),now+=dep[u]-dep[fa[j][u]],u=fa[j][u];
cout<<res<<" \n"[i==q];
}
for(int i=1;i<=n;i++) e[i].clear();
}
signed main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
CF1843F Omsk Metro
给你一棵树,询问 \((u,v)\) 路径序列上权值的子段是否存在和为 \(k\)。权值只有 \(\pm 1\)。
\(n\le 2\times 10^5\)
诈骗题。由于权值为 \(\pm 1\),所以子段和连续,所以树剖一下直接求出路径上的子段和区间 \([l,r]\) 判断 \(k\) 是否在里面即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
#define ls (now<<1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
int n,q;
int w[maxn];
vector<int>e[maxn];
int ql[maxn],qr[maxn],qk[maxn],qcnt;
int top[maxn],dfncnt,idx[maxn],dfn[maxn],son[maxn],siz[maxn],fa[maxn],dep[maxn];
void dfs1(int u,int F){
siz[u]=1;
fa[u]=F;
dep[u]=dep[F]+1;
for(int v:e[u]){
if(v!=F){
dfs1(v,u);
if(siz[son[u]]<siz[v]) son[u]=v;
siz[u]+=siz[v];
}
}
}
void dfs2(int u,int t){
top[u]=t;
dfn[u]=++dfncnt;
idx[dfncnt]=u;
if(son[u]) dfs2(son[u],t);
for(int v:e[u]){
if(v!=fa[u]&&v!=son[u]){
dfs2(v,v);
}
}
}
struct node{
int st;
int sum,pre,suf;
int sum1,pre1,suf1;
node(int st=0,int sum=0,int pre=0,int suf=0,int sum1=0,int pre1=0,int suf1=0): st(st),sum(sum),pre(pre),suf(suf),sum1(sum1),pre1(pre1),suf1(suf1){}
node friend operator+(const node &a, const node &b){
node c;
c.st=a.st+b.st;
c.sum=max({0,a.sum,b.sum,a.suf+b.pre});
c.pre=max({0,a.pre,a.st+b.pre});
c.suf=max({0,b.suf,b.st+a.suf});
c.sum1=min({0,a.sum1,b.sum1,a.suf1+b.pre1});
c.pre1=min({0,a.pre1,a.st+b.pre1});
c.suf1=min({0,b.suf1,b.st+a.suf1});
return c;
}
}tr[maxn<<2];
void upd(node &x,node y){x=x+y;}
void upd1(node &x,node y){x=y+x;}
void pushup(int now){
tr[now]=tr[ls]+tr[rs];
}
void build(int now,int l,int r){
if(l==r){
tr[now].st=w[idx[l]];
tr[now].sum=max(0,w[idx[l]]);
tr[now].pre=max(0,w[idx[l]]);
tr[now].suf=max(0,w[idx[l]]);
tr[now].sum1=min(0,w[idx[l]]);
tr[now].pre1=min(0,w[idx[l]]);
tr[now].suf1=min(0,w[idx[l]]);
return;
}
build(ls,l,mid); build(rs,mid+1,r);
pushup(now);
}
node query(int now,int l,int r,int L,int R){
if(L<=l&&r<=R){
return tr[now];
}
node res;
if(L<=mid) upd(res,query(ls,l,mid,L,R));
if(mid+1<=R) upd(res,query(rs,mid+1,r,L,R));
return res;
}
node query_tr(int u,int v){
node res,re1;
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]]){
upd1(res,query(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}else{
upd1(re1,query(1,1,n,dfn[top[v]],dfn[v]));
v=fa[top[v]];
}
}
if(dep[u]>dep[v]){
upd1(res,query(1,1,n,dfn[v],dfn[u]));
}else{
upd1(re1,query(1,1,n,dfn[u],dfn[v]));
}
swap(res.pre,res.suf);
swap(res.pre1,res.suf1);
upd(res,re1);
return res;
}
void solve(){
cin>>q; dfncnt=qcnt=0;n=1;w[1]=1;
for(int i=1,u,v,k;i<=q;i++){
char op;
cin>>op;
if(op=='+'){
cin>>v>>k;
n++; w[n]=k;
e[v].emplace_back(n);
e[n].emplace_back(v);
}else{
cin>>u>>v>>k;
qcnt++;
ql[qcnt]=u;
qr[qcnt]=v;
qk[qcnt]=k;
}
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=qcnt;i++){
node p=query_tr(ql[i],qr[i]);
if(!qk[i]||(p.sum1<=qk[i]&&qk[i]<=p.sum)) cout<<"YES\n";
else cout<<"NO\n";
}
for(int i=1;i<=n;i++){
son[i]=0;
e[i].clear();
}
}
signed main(){
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
CF1042E Vasya and Magic Matrix
一个 \(n\) 行 \(m\) 列的矩阵,每个位置有权值。给定一个出发点 \((s,t)\),每次可以等概率的移动到一个权值小于当前点权值的点(不能移动就停止),同时得分加上两个点之间欧几里得距离的平方,问得分的期望,对 998244353 取模。
\(n,m\le 10^3\)
显然把小于出发点权值的位置拎出来,形成一个序列(设长为 \(d\)),左边的可以走到右边。考虑期望 DP。设 \(f(i)\) 表示从 \(i\) 出发的距离期望,则有
\(nxt(i)\) 为第一个 \(v_j<v_i\) 的位置。
把 dis 拆成四个部分,考虑后缀和优化一下就搞完了。时间复杂度 \(O(nm\log nm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e3+7;
const int mod=998244353;
int n,m,s,t;
int a[maxn][maxn];
struct node{
int x,y,val;
bool operator<(const node &o)const{return val>o.val;}
}b[maxn*maxn];
int cnt;
int f[maxn*maxn],g[maxn*maxn],inv[maxn*maxn];
int prex[maxn*maxn],prexx[maxn*maxn],prey[maxn*maxn],preyy[maxn*maxn];
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
void mul(int &x,int y){x=x*y%mod;}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
inv[1]=1;
for(int i=2;i<=n*m;i++)
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
cin>>s>>t;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]<a[s][t]) b[++cnt]={i,j,a[i][j]};
b[++cnt]={s,t,a[s][t]};
sort(b+1,b+cnt+1);
for(int i=cnt;i;i--){
prex[i]=(prex[i+1]+b[i].x)%mod;
prexx[i]=(prexx[i+1]+b[i].x*b[i].x)%mod;
prey[i]=(prey[i+1]+b[i].y)%mod;
preyy[i]=(preyy[i+1]+b[i].y*b[i].y)%mod;
}
int pos=cnt;
for(int i=cnt;i;i--){
if(b[i].val==b[cnt].val) continue;
if(b[i].val!=b[i+1].val) pos=i+1;
add(f[i],(cnt-pos+1)*b[i].x*b[i].x%mod);
add(f[i],(cnt-pos+1)*b[i].y*b[i].y%mod);
add(f[i],prexx[pos]);
add(f[i],preyy[pos]);
add(f[i],mod-2*b[i].x*prex[pos]%mod);
add(f[i],mod-2*b[i].y*prey[pos]%mod);
add(f[i],g[pos]);
mul(f[i],inv[cnt-pos+1]);
g[i]=(g[i+1]+f[i])%mod;
}
cout<<f[1];
return 0;
}
CF1174E Ehab and the Expected GCD Problem
设 \(f(A)\) 为 \(A\) 序列的前缀 gcd 中不同数值的数量。求对于长为 \(n\) 的排列,求出 \(f(A)\) 最大的排列数量。
\(n\le 10^6\)
手玩/爆搜小样例可以发现,gcd 以每次 ÷2 或 3 的速度递减,即 \(A_1=2^x3^y\),由于 \(2^3<3^2\) 所以 \(y\le 1\)。且 \(A_1=2^x\) 最优,若 \(2^{x-1}3\le n\) 则也不劣。记 \(p(x)=n/x\) 为 \(x\) 的倍数数量,于是考虑 DP,设 \(f(i,x,y)\) 为填数到第 \(i\) 位,当前 gcd 为 \(2^x3^y\) 的方案数。
若 gcd 不变则可以填 \(p(2^x3^y)\) 个数,由于前面已经填了 \(i-1\) 个数,所以可以填 \(p(2^x3^y)-(i-1)\) 个数。
若 gcd 变成 gcd÷2 则可以填 \(p(2^{x-1}3^y)\) 个数,由于填 \(p(2^x3^y)\) gcd 不会变,所以只能填 \(p(2^{x-1}3^y)-p(2^x3^y)\) 个数。
若 gcd 变成 gcd÷3 则可以填 \(p(2^x3^{y-1})\) 个数,由于填 \(p(2^x3^y)\) gcd 不会变,所以只能填 \(p(2^x3^{y-1})-p(2^x3^y)\) 个数。
则 \(f(i,x,y)\leftarrow f(i-1,x,y),f(i-1,x-1,y),f(i-1,x,y-1)\)。
时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
const int mod=1e9+7;
int n;
int f[maxn][20][2],g[2]={1,3};
int lg2[maxn];
int biao[]={0,1,1,4,2,6,120,600,240,1440,15120,120960,7015680};
int p(int y){return n/y;}
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
signed main(){
cin>>n;
if(n<=12){return cout<<biao[n],0;}
for(int i=2;i<=n;i++)
lg2[i]=lg2[i>>1]+1;
f[1][lg2[n]][0]=1;
if((1<<(lg2[n]-1))*3<=n) f[1][lg2[n]-1][1]=1;
for(int i=2;i<=n;i++){
for(int j=lg2[n];~j;j--){
for(int k=1;~k;k--){
add(f[i][j][k],1ll*f[i-1][j][k]*(p((1<<j)*g[k])-i+1)%mod);
if(j!=lg2[n]) add(f[i][j][k],1ll*f[i-1][j+1][k]*(p((1<<j)*g[k])-p((1<<(j+1))*g[k]))%mod);
if(!k) add(f[i][j][k],1ll*f[i-1][j][k+1]*(p(1<<j)-p((1<<j)*3))%mod);
}
}
}
cout<<f[n][0][0];
return 0;
}