Codeforces Round #813(Div.2) 题解
ABC 送分题,B的证明好像有点有趣,不过结论猜就完了
A
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9;
int n,k;
int a[105];
void solve(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int p = k, cnt=0;
for(int i=1;i<=k;i++){
if(a[i] > p)++ cnt;
}
printf("%d\n",cnt);
}
signed main(){
int te;scanf("%d",&te);
while(te --)solve();
return 0;
}
B
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9;
void solve(){
int n;
scanf("%d",&n);
if(n%2==0){
for(int i=1;i<=n;i+=2)printf("%d %d ",i+1,i);
puts("");
}else{
printf("1 ");
for(int i=2;i<=n;i+=2)printf("%d %d ",i+1,i);
puts("");
}
}
signed main(){
int te;scanf("%d",&te);
while(te--)solve();
return 0;
}
C
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9;
int n;
int a[100005],lt[100005],vis[100005],mx[100005];
void solve(){
memset(lt,0,sizeof lt);
memset(vis,0,sizeof vis);
memset(mx,0,sizeof mx);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int lst=-1;
for(int i=n;i>=2;i--){
if(a[i] < a[i-1]){lst = i-1;break;}
}
for(int i=1;i<=n;i++)lt[a[i]] = i;
for(int i=1;i<=n;i++)mx[i] = max(mx[i-1],lt[a[i]]);
if(lst == -1){puts("0");return ;}
for(int i=lst;i<=n;i++){
if(lt[a[i]]!=i)continue;
if(mx[i] == i){
int cnt=0;
for(int j=1;j<=i;j++){
if(!vis[a[j]])++cnt,vis[a[j]]=1;
}
printf("%d\n",cnt);
return ;
}
}
}
signed main(){
int te;scanf("%d",&te);
while(te --)solve();
return 0;
}
D
首先注意到对于一个给定序列\(a_1..a_n\),答案就是
解释:首先我们要最大化的就是某两点最短路的最大值。考虑最小值\(a_x\)的位置,如果\(x\)位于\([l,r]\)之间,那么根据定义,\(l\)与\(r\)之间连一条长为最小值的边,易知这一定是l和r的最短路长度,显然不是我们想要的,因为我们要的是最短路的最大值。如果\(x \notin [l..r]\),我们考虑l和r的最短路长什么样,考虑两种情况:$l \rightarrow p\rightarrow r $ 显然我们取能包含住\(x\)的\([p..l]\&[p..r]\)就能取到最小值,即为\(2*a_x\),显然这是这种情况最小值,第二种情况是直接\(l\rightarrow r\),长度也很显然,是\(\min{a[l..r]}\),最短距离即为二者最小值
现在我们想要最大化这个最小值,怎么做?显然第一种情况不可能再小了,只能第二种情况变大,注意到\(\min a[l..r] \leq \min a[l+1..r]\),也就是说我们应该尽量缩小l r距离,显然当\(r=l+1\)可以。也就是得到了上式
如何利用好赋值?普通的贪心是会有反例的,但是我们发现,对于某一个答案来说,如果他能够通过\(k\)次赋值得到,那么比这个答案更小的一定也能不多于\(k\)次赋值得到,可以二分。
于是二分答案,判断修改次数是否小于\(k\)即可
(考场尝试了3次贪心都错了...)
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9,maxn=100005;
int n,k;
int a[maxn],b[maxn];
int check(int x){
for(int i=1;i<=n;i++)b[i]=a[i];
int tot=0;
for(int i=1;i<=n;i++)
if(a[i]*2 < x)++ tot, b[i] = 1e9;
int res=2;
for(int i=1;i<n;i++){
res = min(res, (b[i]<x) + (b[i+1]<x));
}
return res+tot <= k;
}
void solve(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int l=1,r=1e9,mid,ans;
while(l <= r){
mid = l+r>>1;
if(check(mid))l = mid+1,ans=mid;
else r = mid-1;
}
printf("%d\n",ans);
}
int main(){
int te;scanf("%d",&te);
while(te --)solve();
return 0;
}
E1&E2
容斥一下,就转化为了求\(lcm(i,j,k)<i+j+k\)的方案
转化的目的是为了放缩
即\(lcm(i,j,k)<i+j+k<3k\),又因为是\(k\)的倍数,必然有\(lcm(i,j,k)=k\) 或者 \(lcm(i,j,k)=2k且i+j>k\)(i+j>k是由2k<i+j+k得到)
先考虑第一种情况,显然\(i\)和\(j\)都是\(k\)的约数,对于每个\([l..r]\)我们可以暴力枚举\(k\),统计在\([l,k)\)的\(k\)的约数个数,记为\(cnt\),则其对答案的贡献即为\(\binom{cnt}{2}\)
第二种情况?首先\(i\)和\(j\)都是\(2k\)的约数,易知\(j\geq\)\(\frac{k}{2}\),所以\(j\)只可能取\(\frac{2k}{3}\)(如果为整数),将\(j\)代回不等式反解,\(\frac{k}{3}<i<\frac{2k}{3}\),\(i\)可取\(\frac{2k}{5}和\frac{k}{2}\),如果为整数,对于每个\(k\)额外判断一下即可
求出答案之后\(C_{r-l+1}^3-方案\)即可
时间复杂度\(O(Tnlogn)\),可以过E1
对于\(T\leq 1e5\),考虑离线,按照\(l\)从大到小排序
维护一个指针,从大到小扫描,对于某个位置\(cur\),考虑\(cur*2, cur*3, ...\)(相当于E1我是枚举因数,E2我是枚举倍数,对于每个因数只需要枚举一次)每次枚举到某个倍数\(m*cur\),第一次+0,第二次+1,...这样就相当于统计了E1中的\(\binom{cnt}{2}\)(为什么第二次才+1?因为\(m*cur\)相当于枚举的是\(k\),而枚举到\(k\)的次数就相当于在[l..k)间\(k\)的因子个数cnt,第二次枚举到相当于有2个这种因数,\(C_2^2=1, C_3^2=3=1+2, ...\))
查询的时候就相当于\([1..r]\)的区间和,因为我\(l\)从大到小的,所以不存在不合法情况
树状数组维护即可
考虑对于\([l..r]\)第二种情况的影响,对于\(2/5k, 2/3k, k\)的形式,\(k\)必然为15的倍数,有约束\(k\leq r且2/5k \leq l-1(化归为k\leq\frac{5*(l-1)}{2})\),对于\(k\leq m\),k是15的倍数,k的个数为\(m/15\),根据这个统计答案,对于\(1/2k, 2/3k, k\)同理
E1
// by Balloons
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9,maxn=2e5+5;
vector<int>divsor[maxn];
void solve(){
int l,r;scanf("%d%d",&l,&r);
LL res=0;
for(int i=l+2;i<=r;i++){
int k = i,cnt=0;
for(int tp=0;tp<divsor[k].size();tp++){
int cur = divsor[k][tp];
if(l<=cur && cur<k)++ cnt;
}
res += 1ll*cnt*(cnt-1)/2;
if(k%15==0 && 2*k/5 >=l)++ res;
if(k%6 == 0 && k/2 >=l) ++ res;
}
int len = r-l+1;
LL ans = 1ll*len*(len-1)*(len-2) / 6;
printf("%I64d\n",ans - res);
}
signed main(){
for(int i=1;i<=200000;i++){
for(int j=i;j<=200000;j+=i){
divsor[j].push_back(i);
}
}
int te;scanf("%d",&te);
while(te--)solve();
return 0;
}
E2
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9,maxn=2e5+5;
struct node{int l,r,id;}a[maxn];
int cmp(node a,node b){return a.l > b.l;}
int buc[maxn],bit[maxn];
LL qu[maxn];
int lowbit(int x){return x&(-x);}
void add(int x,int tp){for(int i=x;i<=200000;i+=lowbit(i))bit[i] += tp;}
LL query(int x){LL res=0;for(int i=x;i;i-=lowbit(i))res += bit[i];return res;}
signed main(){
int T;scanf("%d",&T);
for(int i=1;i<=T;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id = i;
}
sort(a+1,a+T+1,cmp);
int curid = 200001;
for(int i=1;i<=T;i++){
int lx = a[i].l, ly = a[i].r, lid = a[i].id;
while(curid>lx){
-- curid;
for(int t=curid*2;t<=200000;t+=curid){
add(t,buc[t]);
++ buc[t];
}
}
int len = ly-lx+1;
LL ans = 1ll*len*(len-1)*(len-2)/6;
ans -= query(ly);
ans -= max(0, ly/15 - (lx-1) / 6);
ans -= max(0ll, ly/6 - 1ll*(lx-1) / 3);
qu[lid] = ans;
}
for(int i=1;i<=T;i++)printf("%I64d\n",qu[i]);
return 0;
}