Ozon Tech Challenge 2020 Div.1 + Div.2,

传送门

A

​ 口水题,在保证所有数互不相同的前提下显然排序可以让和同样互不相同。

B

题目大意

​ 给定一个括号序列,每次操作我们可以从中选出一个长度为\(2x\)的子序列,满足前\(x\)个为左括号,后\(x\)个括号为右括号;求最小操作次数并输出操作内容。

题目分析

​ 在删除时只需要考虑括号的位置,而且在删除之后不会影响其他括号之间的相对位置,也就是说如果某些括号可以被删除,那么这些括号就可以同时删除,那么答案只有可能是\(1\)或者\(0\)

​ 不可以删除,相当于当前序列为空或者形如)))(((。也就是说没有任何可以匹配的括号。

​ 那么我们从序列开头与末尾指针扫过去,一一匹配可行的括号,最后统一输出即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<string.h>
#include<cmath>
#include<deque>
#include<vector>
#include<map>
#define Lint  long long
#define pi acos(-1.0)
using namespace std;
string s;
vector<int>v1,v2;
int main(){
    cin>>s;
    int len=s.length();
    int le=0,re=len-1;
    while(le<re){
        while(le<len&&s[le]==')'){
            le++;
        }
        while(re>=0&&s[re]=='('){
            re--;
        }
        if(le<len&&re>=0&&le<re){
            v1.push_back(le+1);
            v2.push_back(re+1);
            le++;
            re--;
        }
    }
    if(!v1.size()||!v2.size()){
        puts("0");
        return 0;
    }
    puts("1");
    printf("%d\n",2*v1.size());
    for(auto i:v1){
        printf("%d ",i);
    }
    for(int i=v2.size()-1;i>=0;--i){
        printf("%d ",v2[i]);
    }
    return 0;
}

C

题目大意

​ 给定长度为\(n\)序列,求\(∏_{1≤i<j≤n}|ai−aj|\)。对m取模。

题目分析

​ 注意到\(n\)最大\(2e5\)\(m\)最大\(1000\)。那么对于\(n>m\)时根据抽屉原理必然会有至少两数取模\(m\)相同,则答案为\(0\)\(n<=m\)时暴力操作即可,复杂度\(O(m^2)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<string.h>
#include<cmath>
#include<deque>
#include<vector>
#include<map>
#define Lint  long long
#define pi acos(-1.0)
using namespace std;
const int N=2e5+10;
int a[N],n,m;
int main(){
    cin>>n>>m;
    int ans=1;
    for(int i=1;i<=n;++i) cin>>a[i];
    if(n<=m){
        for(int i=1;i<n;++i){
            for(int j=i+1;j<=n;++j){
                ans=ans*(abs(a[i]-a[j])%m);
                ans%=m;
            }
        }
        printf("%d\n",ans);
    }
    else printf("0");
    return 0;
}

D

​ 交互题,考虑任意两叶子节点的祖先节点,若为两点之一则答案为那个点,否则可以直接删除。

​ 注意在交互时c++应额外使用fflush(stdout)std::cout << std::flush刷新输出缓冲。

​ 更详细可见codeforces上的说明

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<string.h>
#include<cmath>
#include<deque>
#include<vector>
#include<map>
#define Lint  long long
#define pi acos(-1.0)
using namespace std;
const int N=1005;
int n,In[N];
char op[5];
int vis[N];
vector<int>G[N];
void solve(){
    int tot=0,x;
    int a[5];
    while(true){
        tot=0;
        for(int i=1;i<=n;++i){
            if(tot==2){
                break;
            }
            if((In[i]==1||In[i]==0)&&!vis[i]){
                a[++tot]=i;
            }
        }
        if(tot==1){
            printf("! %d\n",a[tot]);
            return;
        }
        printf("? %d %d\n",a[1],a[2]);
        fflush(stdout);
        scanf("%d",&x);
        fflush(stdout);
        if(x==a[1]||x==a[2]){
            printf("! %d\n",x);
            return;
        }
        else{
            vis[a[1]]=vis[a[2]]=1;
            for(auto i:G[a[1]]){
                In[i]--;
            }
            for(auto i:G[a[2]]){
                In[i]--;
            }
            In[a[1]]=0;
            In[a[2]]=0;
        }
    }
 
}
int main(){
    int x,y;
    scanf("%d",&n);
    for(int i=1;i<n;++i){
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        In[x]++;
        In[y]++;
        G[y].push_back(x);
    }
    solve();
    return 0;
}

E

题目大意

​ 构造长度为\(n\)的递增数列,满足每个数\(a_i<=1e9\),且存在\(m\)\((i,j,k),i<j<k\),使得\(a_i+a_j=a_k\)

题目分析

​ 显然对于\(a_i=i\)时可构造数目最大,\(i\)位置提供\(\lfloor(i+1)/2\rfloor\)的贡献。但此时贡献可能在某位置超过\(m\),考虑如何使得某位置可行三元组数量减少\(k\)。设当前为第\(j\)个数,初始为\(j\)。考虑提供贡献的组为\((1,j-1),(2,j-2),(3,j-3)\)。那么假如首元素最小的可行组为\((1+2k,j-1)\),那么在其之前就有\(2k\)个数无法被使用,贡献为\(\lfloor(j-1-2k)/2\rfloor\)。失去了\(k\)个贡献。在\(j\)之后的位置可以直接设置为不可能存在贡献的模式即可。可设\(a_n=1e9\)\(a_i=a_{i+1}-2*a_j\)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
const int N = 5e3 + 10;
int a[N], n, m, sum;
bool flag = false;
int main()
{
    scanf("%d%d", &n, &m);
    a[n + 1] = 1e9;
    for (int i = 1; i <= n; ++i)
    {
        a[i] = i;
        sum += (i - 1) / 2;
        if (sum >= m)
        {
            a[i] += (sum - m) * 2;
            for (int j = n; j > i; --j)
            {
                a[j] = a[j + 1] - 2 * a[i];
            }
            flag = true;
            break;
        }
    }
    if (!flag)
    {
        printf("-1\n");
    }
    else
        for (int i = 1; i <= n; ++i)
            printf("%d ", a[i]);
    return 0;
}

G

题目大意

​ 给定\(n\)个点,每点都有其权值\(a_i\),当且仅当\(a_i\&a_j=0\)\(i\)\(j\)之间有边相连。将某点\(i\)染色后,\(i\)可以将与其直接相连并未被染色的点染色,每进行一次染色操作就可获得等同于其权值\(a_i\)的奖励。求最大的总共奖励。

题目分析

​ 假定我们已经知道了整个图的构造,接下来需要求出最大总奖励。先给出结论:假设图上连接\((i,j)\)的边权值为\(a_i+a_j\),那么答案就为此图的最大生成树减去所有点权值和。

​ 简要证明一下:设每点度数为\(v_i\),那么总奖励就为\(\sum(v_i-1)a_i=\sum v_ia_i-\sum a_i\)。对于生成树,树上每点对答案贡献的权值取决于自身的度数。

​ 但我们并不知道图的构造,我们可以尝试从最大值枚举\(a_i+a_j\),枚举此数的二进制下子集,并进行并查集缩点合并,复杂度为\(O(3^{18}logn)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
const int N = (1 << 18) + 10;
int n;
Lint ans, Size[N];
struct dsu
{
    int f[N];
    void init()
    {
        for (int i = 0; i < N; ++i)
        {
            f[i] = i;
        }
    }
    int Find(int x)
    {
        if (x == f[x])
            return x;
        return f[x] = Find(f[x]);
    }
    void marge(int x, int y, Lint w)
    {
        if (Size[x] && Size[y])
        {
            int le = Find(x), re = Find(y);
            if (le != re)
            {
                ans += (Size[le] + Size[re] - 1) * w;
                f[le] = re;
                Size[re] = 1;
            }
        }
    }
} G;
int main()
{
    int x;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &x);
        Size[x]++;
        ans -= x;
    }
    G.init();
    Size[0]++;
    for (int i = (1 << 18) - 1; i >= 0; --i)
    {
        for (int u = i; u > 0; u = (u - 1) & i)
        {
            int v = u ^ i;
            if(Size[u]&&Size[v]){
                G.marge(u, v, i);
            }
        }
    }
    printf("%lld\n", ans);
    return 0;
}

\(Size_i\)所存储的是权值为\(i\)的独立集合个数,两者合并后独立集合便为\(1\),为了处理森林的情况,假定虚点\(a_{n+1}=0\)。因为枚举时必然能出现\(0\)的情况。

posted @ 2021-09-09 20:10  wzyyy  阅读(38)  评论(0)    收藏  举报