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\)的情况。