Codeforces Round #813 (Div. 2)
Preface
这场的A/B/C/E1是8/16号打的,结果因为耽搁剩下的E2/D都是8/27号才打
因为今天白天没有报到任务因此很闲地在寝室颓废了一天,晚上痛心疾首地稍微写一下代码
由于记忆比较模糊(甚至题意都想不起来了),因此会比较简略
A. Wonderful Permutation
SB题,统计前\(k\)个数里有多少个大于\(k\)的即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,a[N],k;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
int c=0; for (i=1;i<=k;++i) if (a[i]<=k) ++c; printf("%d\n",k-c);
}
return 0;
}
B. Woeful Permutation
SB题,不难发现由于\(\operatorname{lcm}(a,b)\le a\cdot b\),且\(\operatorname{lcm}(x,x+1)=x\cdot(x+1)\),因此我们直接从大到小把数两两配对即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=n;i>1;i-=2) a[i]=i-1,a[i-1]=i;
if (n&1) a[1]=1; for (i=1;i<=n;++i) printf("%d%c",a[i]," \n"[i==n]);
}
return 0;
}
C. Sort Zero
首先我们不难发现除非一个数列原来就合法,否则第一个数是一定要操作的(否则在操作了后面的数后一定存在某个位置为\(0\))
因此我们不妨先操作掉第一个数,考虑从前往后处理,若在某个不为\(0\)的位置后出现了\(0\),那么显然这个位置也是一定要操作的
但是注意一种特殊情况,我们需要记录下合法的后缀位置\(pos\),若当前位置后面没有\(0\)但位置在\(pos\)之前那么也要操作
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],pos,ans,cnt; vector <int> v[N];
inline void erase(CI x)
{
for (int y:v[x]) a[y]=0,++cnt;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
bool flag=1; for (i=n;i>1&&flag;--i) if (a[i]<a[i-1]) flag=0;
if (flag) { puts("0"); continue; } else pos=i;
for (i=1;i<=n;++i) v[a[i]].push_back(i);
for (ans=1,cnt=0,erase(a[1]),--cnt,i=2;i<=n;++i)
if (!a[i]) --cnt; else if (cnt||i<=pos) ++ans,erase(a[i]),--cnt;
for (i=1;i<=n;++i) v[i].clear(); printf("%d\n",ans);
}
}
D. Empty Graph
之前第一眼看的时候觉得没什么思路就先去做E了,结果发现观察到性质后还是很水的
由于这是一个完全图,因此两个点\(u,v\)间的最短路只有两种情况:
- 直接从\(u\)到\(v\),长度为\(\min_\limits{u\le i\le v} a_i\)
- 借助某个中转点\(w\),走\(u\to w\to v\)的路径,长度为\(2\times a_w\)
对于第一种情况,由于当\(u\)固定时随着的增大\(\min_\limits{u\le i\le v} a_i\)是不升的,因此这部分的值就是\(\max_\limits{1\le i<n} \min(a_i,a_{i+1})\)
对于第二种情况,不难发现中转点必然是最小值点,即这部分的值是\(2\times \min_\limits{1\le i\le n} a_i\)
由于答案的形式是经典的最小值最大,因此考虑二分答案\(x\)
首先考虑第二种情况,若\(2\times a_i<x\),则必须花费一次操作把\(a_i\)变为\(10^9\)
接下来考虑验证\(\max_\limits{1\le i<n} \min(a_i,a_{i+1})\)的值是否大于等于\(x\),是则合法
否则若剩余操作次数大于\(1\),显然合法,若小于等于\(0\)则不合法
否则若剩余操作次数等于\(1\),则考虑找出是否存在\(a_i\ge x\),此时只需要将其相邻的一个数修改为\(10^9\)即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int T,n,k,a[N],t[N];
inline bool check(CI x,int ct=k)
{
RI i; for (i=1;i<=n;++i) if (t[i]*2<x) --ct,t[i]=1e9; if (ct<0) return 0;
int mx=0; for (i=1;i<n;++i) mx=max(mx,min(t[i],t[i+1]));
if (mx>=x) return 1; if (ct>1) return 1; if (ct==0) return 0;
for (i=1;i<=n;++i) if (t[i]>=x) return 1; return 0;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&T);T;--T)
{
RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
RI l=1,r=1e9,mid,ans; while (l<=r)
{
for (i=1;i<=n;++i) t[i]=a[i]; mid=l+r>>1;
if (check(mid)) ans=mid,l=mid+1; else r=mid-1;
}
printf("%d\n",ans);
}
}
E1. LCM Sum (easy version)
首先考虑求其反面,用总方案减去不合法的即可,然后我们细细分析发现只有两种不合法的情况:
- \(\operatorname{lcm}(i,j,k)=k\),此时\(i,j\)均为\(k\)的约数
- \(\operatorname{lcm}(i,j,k)=2k\and i+j>k\),此时\(i,j\)均为\(2k\)的约数
否则若\(\operatorname{lcm}(i,j,k)=n\cdot k(n\ge 3)\),由于\(i<j<k\),必然合法
对于第一种情况,我们可以暴力地统计出每个\(k\)在\([l,r]\)中有多少个小于\(k\)的因子,记为\(f_k\),显然\(k\)的贡献就是\(C_{f_k}^2\)
对于第二种情况,可以同上暴枚\(2k\)的因子并统计,预处理一下好像是能\(O(n\sqrt n)\)跑过\(400000\)的
但是我们可以稍加分析,因为\(\frac{k}{2}<j<k\),而\(j|2k\),设\(j=\frac{2k}{p}\),则\(\frac{k}{2}<\frac{2k}{p}<k\),得到\(2<p<4\),则\(p=3,j=\frac{2k}{3}\)
由于\(i|2k\),设\(i=\frac{2k}{q}\),则\(\frac{k}{3}<\frac{2k}{q}<\frac{2k}{3}\),得到\(3<q<6\),则\(q=4/5,i=\frac{k}{2}/\frac{2k}{5}\)
因此可以直接减去对应的情况即可,复杂度\(O(T\times n\sqrt n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,l,r,f[N]; long long ret;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; scanf("%d%d",&l,&r); int len=r-l+1; ret=0;
for (i=l;i<=r;++i) for (j=i<<1;j<=r;j+=i) ++f[j];
for (i=l;i<=r;++i)
{
ret+=1LL*f[i]*(f[i]-1)/2LL; if (i%3) continue;
if (i%2==0&&i/2>=l) ++ret; if (i%5==0&&i/5*2>=l) ++ret;
}
for (i=l;i<=r;++i) f[i]=0;
printf("%lld\n",1LL*len*(len-1)*(len-2)/6LL-ret);
}
return 0;
}
E2. LCM Sum (hard version)
首先我们发现对于\(\operatorname{lcm}(i,j,k)=2k\and i+j>k\)的情况可以进一步优化,此时答案一定是\((3,4,6)/(6,10,12)\)的倍数,因此可以直接计算
然后考虑对于\(\operatorname{lcm}(i,j,k)=k\)的部分,显然对于如此多的询问我们可以考虑离线,并且把\([l,r]\)的询问拆分为\([1,r]\)和\([1,l-1]\)的询问
考虑从小到大加入每个数\(k\),考虑枚举\(i\),此时合法的\(j\)的取值数目就是\(k\)的约数个数(不包括自身)减去\(rank(i)\)(\(rank(i)\)表示\(i\)是\(k\)的约数中第几小的),我们将它更新到\(i\)的贡献上即可
那么此时问题就变成了单点修改,区间求和了,直接上树状数组即可,复杂度\(O(n\sqrt n+T\log n)\)
#include<cstdio>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
#define LL long long
using namespace std;
const int N=200000;
struct ques
{
int id,l,r;
friend inline bool operator < (const ques& A,const ques& B)
{
return A.r<B.r;
}
}q[N+5]; int t; LL ans[N+5]; vector <int> frac[N+5];
class TreeArray
{
private:
long long bit[N+5];
#define lowbit(x) (x&(-x))
public:
inline void add(int x,CI y)
{
for (;x<=N;x+=lowbit(x)) bit[x]+=y;
}
inline LL get(int x,LL ret=0)
{
for (;x;x-=lowbit(x)) ret+=bit[x]; return ret;
}
#undef lowbit
}T;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (i=1;i<=N;++i) for (j=i<<1;j<=N;j+=i) frac[j].push_back(i);
for (scanf("%d",&t),i=1;i<=t;++i)
{
scanf("%d%d",&q[i].l,&q[i].r); q[i].id=i; int len=q[i].r-q[i].l+1;
ans[i]=1LL*len*(len-1)*(len-2)/6LL;
ans[i]-=max(0,q[i].r/6-(q[i].l-1)/3);
ans[i]-=max(0,q[i].r/15-(q[i].l-1)/6);
}
RI now=1; for (sort(q+1,q+t+1),i=1;i<=N;++i)
{
for (j=0;j<frac[i].size();++j) T.add(frac[i][j],frac[i].size()-j-1);
while (now<=t&&q[now].r<=i) ans[q[now].id]-=T.get(q[now].r)-T.get(q[now].l-1),++now;
}
for (i=1;i<=t;++i) printf("%lld\n",ans[i]); return 0;
}
Postscript
大学生活马上就要开始了ovo,明天要考英语的分班测试,直接躺平拿c走人
(因为平时分给的高要求还低,不用折磨像我这样的英语辣鸡)

浙公网安备 33010602011771号