Codeforces Round #613 (Div. 2)

A. Mezo Playing Zoma (CF 1285 A)

题目大意

机器人初始位置为\(0\),给了一串指令告诉机器人是往左走一格还是右走一格,部分指令可能会丢失,问机器人最终可能的位置的情况数。

解题思路

\(L,R\)的个数加一即为答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
int main(void) {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int n;
    string s;
    cin>>n>>s;
    int l=0,r=0;
    for(int i=0;i<n;++i){
        if (s[i]=='L') ++l;
        else ++r;
    }
    int ans=l+r+1;
    cout<<ans<<endl;
}


B. Just Eat It! (CF 1285 B)

题目大意

给定一个数列,问整个区间和以及部分区间和哪个大。

解题思路

区间和最值的用\(dynamic\ programming\),特判最值是否在全部区间取的即可。\(dp[i]\)表示前\(i\)个数,其中第\(i\)个数必取的最大值。则\(dp[i]=\max(dp[i-1],a[i])\),答案为\(\max\limits_{1\leq i\leq n}(dp[i])\)

对应为代码注释部分。

注意到区间可以理解为是去掉从开头的连续几个数以及从结尾的连续几个数。去掉后如果和会变大或者不变,则说明存在某点的前缀和或后缀和小于等于\(0\),如此两边扫一遍也行。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<PII> VPII;
typedef vector<LL> VL;
typedef pair<LL,LL> PLL;
typedef vector<PLL> VPLL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=1e5+8;

int n;

LL a[N];

bool check(){
    LL sum=0;
    for(int i=0;i<n;++i){
        sum+=a[i];
        if (sum<=0) return 0;
    }
    sum=0;
    for(int i=n-1;i>=0;--i){
        sum+=a[i];
        if (sum<=0) return 0;
    }
    return 1;
}

int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; read(kase);
    for (int i = 1; i <= kase; i++) {
        //printf("Case #%d: ", i);
        read(n);
        for(int i=0;i<n;++i) read(a[i]);
        if (check()) puts("YES");
        else puts("NO");
    }
    return 0;
}


/* int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; read(kase);
    for (int i = 1; i <= kase; i++) {
        //printf("Case #%d: ", i);
        int n;
        LL sum=0;
        read(n);
        LL a[n+5]={0};
        for(int i=0;i<n;++i){
            read(a[i]);
            sum+=a[i];
        }
        LL ans=a[0];
        LL dp[n+5]={0};
        dp[0]=a[0];
        bool qwq=true;
        for(int i=1;i<n;++i){
            if (dp[i-1]+a[i]>a[i]) dp[i]=dp[i-1]+a[i];
            else {dp[i]=a[i]; qwq=false;}
            ans=MAX(ans,dp[i]);
        }
        if (qwq) for(int i=0;i<n-1;++i) if (dp[i]==sum) qwq=false;
        if (dp[n-1]==sum&&qwq&&ans==sum) ans=sum-1;
        if (ans>=sum) printf("NO\n");
        else printf("YES\n");
    }
    return 0;
} */


C. Fadi and LCM (CF 1285 C)

题目大意

给定\(x\),找出一对\(a,b\)使得\(lcm(a,b)=x\)\(\max(a,b)\)最小。

解题思路

\(lcm(a,b)=\dfrac{a\times b}{gcd(a,b)}=x\),令\(a=k_1\times gcd(a,b),b=k_2\times gcd(a,b)\),则\(k_1\times k_2\times gcd(a,b)=x\),不失一般性,我们设\(a\leq b\)\(k_1\leq k_2\),我们要最小化\(b\),即最小化\(k_2\times gcd(a,b)\),即最大化\(k_1\),即找到最大的\(k_1\)\(k_1\leq k_2\),由于\(x\leq 10^{12}\),而\(k_1\leq \sqrt{x}\),所以我们从大到小枚举\(k_1\)判断\(lcm(a,b)\)是否等于\(x\)即可。我们也可以假设\(gcd(a,b)=1\),这是始终有解且一定是最小的。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<PII> VPII;
typedef vector<LL> VL;
typedef pair<LL,LL> PLL;
typedef vector<PLL> VPLL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    LL x=0;
    read(x);
    LL qwq=sqrt(x);
    while(qwq){
        if (x%qwq==0) if(__gcd(qwq,x/qwq)==1) break;
        --qwq;
    }
    printf("%lld %lld\n",qwq,x/qwq);
    return 0;
}


D. Dr. Evil Underscores (CF 1285 D)

题目大意

给定\(n\)个数\(a_i\ (i=1,2,3,...,n)\),要求找一个数\(X\),最小化\(\max\limits_{1\leq i\leq n}(a_i\oplus X)\)。输出这个最小值。

解题思路

异或题盲猜trie
然后弄dp然后暴毙
经过分析我们发现,对于在二进制下的某一位,如果全都是\(1\)或者\(0\),我们都可以对\(X\)在这一位添\(1\)\(0\)来变小,但如果某一位既有\(1\)又有\(0\),那这一位将不可避免的存在\(1\)。那么我们从高位起,判断该位,如果全部是\(0\)\(1\),那么答案在该位就是\(0\),否则将是\(1\),然后分\(X\)在该位是\(0\)\(1\)两种情况继续搜下去即可。在\(trie\)树上跑。

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<PII> VPII;
typedef vector<LL> VL;
typedef pair<LL,LL> PLL;
typedef vector<PLL> VPLL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=4e6+7;

int trie[N][2],n,m=1;

void add(int x){
    int t=1;
    for(int i=29;i>=0;--i){
        if (!trie[t][(x>>i)&1]) trie[t][(x>>i)&1]=++m;
        t=trie[t][(x>>i)&1];
    }
}

int DFS(int t,int len){
    if (len<0) return 0;
    if (!trie[t][0]) return DFS(trie[t][1],len-1);
    if (!trie[t][1]) return DFS(trie[t][0],len-1);
    return (min(DFS(trie[t][0],len-1),DFS(trie[t][1],len-1))|(1<<len));
}

int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    read(n);
    for(int u,i=1;i<=n;++i){
        read(u);
        add(u);
    }
    printf("%d\n",DFS(1,29));
    return 0;
}


E. Delete a Segment (CF 1285 E)

题目大意

给定\(n\)条线段,线段之间若有交叉(端点值相同也算交叉)或者重合则可以联合起来成为新的一条(可能)更长的线段,现在要求去掉一条线段(不能不去),使得去掉后联合后的线段最多,问是多少。

解题思路

我们考虑去掉一条线段后能够增加多少个线段。

我们可以发现,如果该线段所覆盖的区间里,存在某个子区间,它只被该线段覆盖的话,去掉该线段后,这里就有一个空隙。而如果有\(x\)个空隙出来的话,那就会多出\(x\)个线段出来。这启示我们从空隙数量的角度来解决联合线段的个数。

我们考虑如何求只被该线段覆盖的区间的数量。

我们按左端点从小到大排列枚举,对于当前枚举的这条线段\(i\),前\(i-1\)条线段的右端点的最大值为\(r\),次大值为\(r_0\),则对于其右端点为\(r\)的某条线段\(j\)来说,它所覆盖的\([l_j,r_j]\)区间中,\((r_0,r_j]\)区间(或\([l_j,r_j]\),如果\(l_j>r_0\))并没有被其他线段覆盖,如果第\(i\)条线段的左端点\(l_i>r_0\)的话,那么区间\((r_0,l_i)\)这部分区间就只被线段\(j\)覆盖,然后我们更新\(r\)\(r_0\)重复操作,得到每条线段覆盖的区间内没被其他线段覆盖的区间数\(cnt_i\)(或者说线段内的空隙),还有本身存在的空隙\(gap\),最后由植树原理得答案\(ans=gap+\max\limits_{1\leq i\leq n}(cnt_i)+1\)

要特判全部线段没有交叉的情况,此时最终答案应是\(ans=n-1\)

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<PII> VPII;
typedef vector<LL> VL;
typedef pair<LL,LL> PLL;
typedef vector<PLL> VPLL;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int kase; read(kase);
    for (int i = 1; i <= kase; i++) {
        //printf("Case #%d: ", i);
        int n;
        vector<pair<int,int>> a;
        read(n);
        for(int l,r,i=0;i<n;++i){
            read(l);
            read(r);
            a.push_back(make_pair(l,r));
        }
        sort(a.begin(),a.end(),less<pair<int,int>>());
        int r=a[0].second,ans=0,ma=-2147483644,cnt=0,gap=0;
        for(int i=1;i<n;++i){
            if (a[i].first>r){
                ++gap;
                r=a[i].second;
                ma=-2147483644;
                ans=max(ans,cnt);
                cnt=0;
            }else if (ma==-2147483644){
                ma=min(r,a[i].second);
                r=max(r,a[i].second);
            }else{
                if (a[i].first>ma) ++cnt;
                if (a[i].second>=r){
                    ans=max(ans,cnt);
                    cnt=0;
                }
                ma=max(ma,min(r,a[i].second));
                r=max(r,a[i].second);
            }
        }
        ans=max(ans,cnt);
        ans=ans+gap+1;
        if (gap+1==n) ans=n-1;
        printf("%d\n",ans);
    }
    return 0;
}


F. Classical? (CF 1285 F)

题目大意

给定\(n\)个数\(a_i\),求\(\max\limits_{1 \le i < j \le n} lcm(a_i,a_j)\)

解题思路

\(lcm(a_i,a_j)=\dfrac{a_i\times a_j}{gcd(a_i,a_j)}\)。我们可以枚举\(gcd(a_i,a_j)\),然后所有数都除以\(gcd(a_i,a_j)\),这样我们要寻找的就是两个互质的数\(a_x,a_y\),它们的乘积最大。我们从大到小遍历这些数,对于当前的数\(a_i\),我们想知道大于它的数中是否有与它互质的,即求\(\sum\limits_{a_j>a_i}[gcd(a_i,a_j)=1]\)是否大于零。这是个经典式子,由于\([gcd(a_i,a_j)=1]=\sum_\limits{d|gcd(a_i,a_j)}\mu(d)\),我们对其变形:

\[\sum\limits_{a_j>a_i}[gcd(a_i,a_j)=1]=\sum\limits_{a_j>a_i}\sum_{d|gcd(a_i,a_j)}\mu(d) \]

我们改变它的求和顺序,由于\(d\)肯定是\(a_i\)的因子,我们枚举它的每一个因子,这样的因子在求和式中出现次数为\(cnt_d\),得

\[\sum\limits_{a_j>a_i}\sum_{d|gcd(a_i,a_j)}\mu(d)=\sum\limits_{d|a_i}\mu(d)*cnt_d \]

其中\(cnt_d\)表示大于\(a_i\)的数中是\(d\)的倍数的个数,\(\mu(x)\)是莫比乌斯函数。

这样我们维护一个\(cnt\)数组,就能够知道是否有与\(a_i\)互质的数,拿一个指针往回扫,扫到那个互质的数,然后计算答案\(a_i*a_j*gcd(a_i,a_j)\)(因为这里的两个数\(a_i,a_j\)都除以了一次\(gcd(a_i,a_j)\))。注意下次再遇到有互质的时候,指针从上次的位置继续往回找即可,不需要重新回到原来位置往回扫,因为我们是从大到小遍历这些数,小的数与前面的数的乘积不可能大于原先的答案,这里可以用栈处理。

时间复杂度为\(O(\sum\limits_{i=1}^{n}\sigma_0(i)^2)\),其中\(\sigma_0(n)=\sum\limits_{d|n}1\)

还可以再优化一下,我们把\(\dfrac{a_i\times a_j}{gcd(a_i,a_j)}\)视为\(a_i\times \dfrac{a_j}{gcd(a_i,a_j)}\),于是把\(a_j\)的每个因子都储存下来,这样虽然会增加原本不应有的答案,但这些答案会小于\(a_j\)对应的答案,对我们要找的最大值无影响。

也就是说我们对于每个\(a_i\),把它的因子\(d_{ij}\)全部加到数组里,因为拿\(a_i\)与另一个数求\(LCM\)不会小于拿它的某个因子\(d_i\)与另一个数求\(LCM\),所以这对最终答案没有影响,尽管会增加原本不会出现的答案。然后再从大到小枚举\(a_i\),寻找比\(a_i\)大的且与\(a_i\)互质的数,这仍是上面的方法,只是省去了枚举\(gcd(a_i,a_j)\)的操作,最终的时间复杂度为\((\sum\limits_{i=1}^{n}\sigma_0(i))\)

神奇的代码
#include <bits/stdc++.h>
#define MIN(a,b) ((((a)<(b)?(a):(b))))
#define MAX(a,b) ((((a)>(b)?(a):(b))))
#define ABS(a) ((((a)>0?(a):-(a))))
using namespace std;

template <typename T>
void read(T &x) {
    int s = 0, c = getchar();
    x = 0;
    while (isspace(c)) c = getchar();
    if (c == 45) s = 1, c = getchar();
    while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    if (s) x = -x;
}

template <typename T>
void write(T x, char c = ' ') {
    int b[40], l = 0;
    if (x < 0) putchar(45), x = -x;
    while (x > 0) b[l++] = x % 10, x /= 10;
    if (!l) putchar(48);
    while (l) putchar(b[--l] | 48);
    putchar(c);
}

const int N=1e5+8;

bool sign[N];

int n,ma,u[N],cnt[N];

LL ans;

vector<int> d[N];

stack<LL> s;

void pre(){
    for(int i=1;i<=ma;++i){
        for(int j=i;j<=ma;j+=i) d[j].push_back(i); 
        if (i==1) u[i]=1;
        else if (i/d[i][1]%d[i][1]==0) u[i]=0;
        else u[i]=-u[i/d[i][1]];
    }
}

int coprime(int x){
    int tmp=0;
    for(int i:d[x])
        tmp+=u[i]*cnt[i];
    return tmp;
}

void updata(int x,int val){
    for(int i:d[x])
        cnt[i]+=val;
}

int main(void) {
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    read(n);
    for(int a,i=1;i<=n;++i){
        read(a);
        sign[a]=true;
        ma=MAX(ma,a);
    }
    pre();
    for(int i=1;i<=ma;++i)
        for(int j=2;i*j<=ma;++j)
            sign[i]|=sign[i*j];
    for(int qwq,i=ma;i>=1;--i){
        if (!sign[i]) continue;
        qwq=coprime(i);
        while(qwq){
            if (__gcd(s.top(),(LL)i)==1){
                ans=max(s.top()*(LL)i,ans);
                --qwq;
            }
            updata(s.top(),-1);
            s.pop();
        }
        updata(i,1);
        s.push((LL)i);
    }
    write(ans,'\n');
    return 0;
}


posted @ 2020-01-11 20:53  ~Lanly~  阅读(744)  评论(0编辑  收藏  举报