Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated, T-shirts + prizes!)

A. Kuroni and the Gifts

题意:T(100)组数据,两组数an,bn,各n(100)个,每组数互不相同,找一种匹配方式使得ai+bj互不相同。

思路:两组数都进行排序,ai和bi匹配得到的就是严格递增的,互不相同。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=105;
17 using namespace std;
18 int T;
19 int n;
20 int a[N],b[N];
21 int main(){
22 // freopen("in.txt","r",stdin);
23  rd(T);
24  while(T--){
25   rd(n);
26   for(int i=1;i<=n;i++)rd(a[i]);
27   for(int i=1;i<=n;i++)rd(b[i]);
28   sort(a+1,a+n+1);sort(b+1,b+n+1);
29   for(int i=1;i<=n;i++)printf("%d ",a[i]);puts("");
30   for(int i=1;i<=n;i++)printf("%d ",b[i]);puts("");
31  }
32  return 0;
33 }
34 /**/
View Code

B. Kuroni and Simple Strings

题意:长度为n(1000)的括号串,每次操作可以选择一个子序列(下标不连续),这个子序列的前一半是'(',后一半是')',将其删除。问最少几次操作可以使得再也无法进行下一次操作。

思路:结论:答案小于等于1。证明:设所有'('的坐标为ai,所有')'的坐标为bi,分别进行排序,我们找到最后一个a[i] < b[cnt_b-i+1]的i,将ai的前缀及对应b的后缀删除,剩下的序列中最左面的'('也在最右面的')'的右面。所以一次操作即可完成,而如果一开始就是不能操作的序列,答案就是0次。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=1005;
17 using namespace std;
18 int n;
19 char s[N];
20 int a[N],b[N],cnt1,cnt2;
21 int main(){
22 // freopen("in.txt","r",stdin);
23  scanf("%s",s+1);
24  n=strlen(s+1);
25  for(int i=1;i<=n;i++)
26   if(s[i] == '(')a[++cnt1]=i;
27   else b[++cnt2]=i;
28  if(!cnt1 || !cnt2 || a[1] > b[cnt2])printf("0\n");
29  else {
30   int tmp;
31   for(int i=1;i<=min(cnt1,cnt2);i++)if(a[i] < b[cnt2-i+1])tmp=i;else break;
32   printf("1\n%d\n",tmp*2);
33   for(int i=1;i<=tmp;i++)printf("%d ",a[i]);
34   for(int i=tmp;i>=1;i--)printf("%d ",b[cnt2-i+1]);
35   puts("");
36  }
37  return 0;
38 }
39 /**/
View Code

 

C. Kuroni and Impossible Calculation

题意:n(2e5)个数字,求两两数之间差的绝对值的乘积对m(1000)取模后的答案。

思路:如果存在两个数字对m取模后相同,那么最终答案就是0,容斥原理很容易可以发现,如果n > m那么一定是0,n < m暴力即可。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=2e5+10;
17 const int M=1e3+10;
18 using namespace std;
19 int n,m;
20 int a[N];
21 int main(){
22 // freopen("in.txt","r",stdin);
23  rd(n);rd(m);
24  for(int i=1;i<=n;i++)rd(a[i]);
25  if(n > m)printf("0\n");
26  else {
27   int ans=1;
28   for(int i=1;i<=n;i++)
29    for(int j=i+1;j<=n;j++)
30     ans=abs(a[i]-a[j])%m*ans%m;
31   printf("%d\n",ans);
32  }
33  return 0;
34 }
35 /**/
View Code

反思:最开始我考虑将所有数对m取模后的值统计一下数量,然后对这1000个数之间两两组合计算答案,后来发现并不可以,因为二者之差的绝对值对m取模与二者对m取模后之差的绝对值并不一定相同。可能是x和m-x的关系。比如13 15 7。原数之间的计算加入了绝对值,可以认为是大减小,而大的数对m取模后不一定还大,它导致i和j的组合中一部分应该是大减小对m取模,另一部分应该是小减大对m取模,并不能都按大减小。而要处理这个问题就很麻烦,所以这个思路并不好。

D. Kuroni and the Celebration

题意:交互题。给一棵n(1000)个节点的树,每次你可以询问两个点的lca,系统将返回lca是什么,需要你在n/2次询问之内找出树的根。

思路:先证明几个简单的结论,一棵树两个节点以上的树至少有两个叶子(度数为1的点),考虑一个一个点加上去的过程即可发现。如果两个叶子的lca是他们其中的一个,那么lca必是根,不妨设xy的lca是y,那么y必然是x的祖先,而如果y还有父亲,那么它一定不是叶子节点,所以他没有父亲,是根。如果两个叶子的lca不是他们其中的一个,那么他们两个都不可能是根,因为如果任何节点和根的lca都是根本身。所以每次枚举两个叶子节点,求出lca,如果不是二者之一就删除这两个点继续找,如果是就找到了答案,需要注意最后可能还剩下一个点,那么这个点就是根。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=1005; 
17 using namespace std;
18 int n,r;
19 int in[N];
20 int fi[N],nxt[N<<1],to[N<<1],tot;
21 void link(int x,int y){nxt[++tot]=fi[x];fi[x]=tot;to[tot]=y;}
22 queue<int>S;
23 int main(){
24 // freopen("in.txt","r",stdin);
25  rd(n);
26  for(int i=1;i<n;i++){
27   int x,y;rd(x);rd(y);
28   link(x,y);link(y,x);
29   in[x]++;in[y]++;
30  }
31  for(int i=1;i<=n;i++)if(in[i] == 1)S.push(i);
32  bool flg=0;
33  while(S.size() >= 2){
34   int u=S.front();S.pop();
35   int v=S.front();S.pop();
36   printf("? %d %d\n",u,v);
37   fflush(stdout);
38   int lc;rd(lc);
39   if(lc == u || lc == v){
40    printf("! %d\n",lc);
41    flg=1;break;
42   }
43   for(int i=fi[u];i;i=nxt[i]){
44    in[to[i]]--;
45    if(in[to[i]] == 1)S.push(to[i]);
46   }
47   for(int i=fi[v];i;i=nxt[i]){
48    in[to[i]]--;
49    if(in[to[i]] == 1)S.push(to[i]);
50   }
51  }
52  if(!flg)printf("! %d\n",S.front());
53  return 0;
54 }
55 /**/
View Code

反思:注意一下交互题的写法吧。每次输出给系统询问的时候,需要加上fflush(stdout);然后再从系统读入数据。

E. Kuroni and the Score Distribution

题意:构造一个长度为n(5000)的严格单调递增序列,使得存在m(1e9)组ijk满足i<j<k,ai+aj=ak,不存在合法情况输出-1。

思路:考虑上界,每个k最多由(k-1)/2组ij组合而成,容易发现ai=i就可以达到这个上界。如果m大于这个上界,那么就不存在合法情况。如果m小于等于这个上界,找到小于m的里面最大的k使得1~k满足ai=i时得到的答案数。接下来考虑构造第k+1位,假设还需要构造o组ai+aj=ak的方案。如果用前2o个数字构造,则无法满足严格单增的情况,那么可以取前k个中的后2o个数字来构造出k+1的值,至于k+2到最后的值,只要取一个比较大的数并且两两之差也比较大的数字即可。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=5e3+10;
17 using namespace std;
18 int n,m;
19 int s[N];
20 int main(){
21 /// freopen("in.txt","r",stdin);
22  rd(n);rd(m);
23  for(int i=1;i<=n;i++)s[i]=s[i-1]+(i-1)/2;
24  if(m > s[n])printf("-1\n");
25  else {
26   for(int i=1;i<=n;i++){
27    if(s[i] <= m)printf("%d ",i);
28    else {
29     if(s[i-1] <= m){
30      int tmp=m-s[i-1];
31      if(!tmp)printf("%d ",(int)(1e8)+i*(int)(1e4));//这里不强制转换会变成乱码,需要注意,原因未确定 
32      else printf("%d ",i-1+i-2*tmp);
33     }
34     else printf("%d ",(int)(1e8)+i*(int)(1e4));
35    }
36   }
37   puts("");
38  }
39  return 0;
40 }
41 /**/
View Code

反思:1e4这种数字都要强制类型转换,不然会出现乱码,具体原因尚不明确。还是要通过极特殊情况寻找界限。

F. Kuroni and the Punishment

题意:n(2e5)个数字,每次操作是对一个数+1或者-1,问最少几次操作可以使得所有数的最大公约数不为1。

思路:首先要知道最终的公约数一定是一个质数,不然取它的某个质因子一定存在更优解。如果我们知道最大公约数是多少,我们就可以在On的时间内得到最少的操作数,即x%m和m-x%m之间取较小值(需要特判x<m的情况防止将x变为0)。考虑如果这个约数是2,那么最多操作n次,我们得到了一个不严格的上界。那么最终答案对应操作数大于等于2的数字一定不足n/2个,也就是说操作数小于等于1的数字大于n/2个。那么我们任取一个数,就有一半以上的概率取到这种数字,我们对a[u],a[u]+1,a[u]-1进行质因数分解,枚举每个因子即可对答案进行一次更新。这次更新有超过1/2的概率使得答案为最终答案,那么我进行该操作若干次,比如20次,错误的概率就变得极小,也就是可以认为得到了正确的答案。

 1 #include<bits/stdc++.h>
 2 #define LL long long
 3 #define dl double
 4 void rd(int &x){
 5  x=0;int f=1;char ch=getchar();
 6  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
 8 }
 9 void lrd(LL &x){
10  x=0;int f=1;char ch=getchar();
11  while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
12  while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f;
13 }
14 const int INF=1e9;
15 const LL LINF=1e18;
16 const int N=2e5+10;
17 using namespace std;
18 int n;
19 LL a[N];
20 int Rand(int x){
21  return ((rand()<<10)+rand())%x;
22 }
23 int ANS;
24 void get_ans(LL x){
25  LL ans=0;
26  for(int i=1;i<=n;i++)
27   if(a[i] < x)ans+=x-a[i];
28   else ans+=min(a[i]%x,x-a[i]%x);
29  if(ans > n)return ;
30  ANS=min(ANS,(int)ans);
31 }
32 void work(LL x){
33  int l=sqrt(x);
34  for(int i=2;i<=l;i++){
35   if(x % i)continue;
36   while(x % i == 0)x/=i;
37   get_ans(i);
38  }
39  if(x != 1)get_ans(x);
40 }
41 int main(){
42 // freopen("in.txt","r",stdin);
43  rd(n);ANS=0x7fffffff;
44  for(int i=1;i<=n;i++)lrd(a[i]);
45  for(int i=1;i<=20;i++){
46   int u=Rand(n)+1;
47   if(a[u] > 1)work(a[u]);//防止出现0,1 
48   if(a[u] > 0)work(a[u]+1);
49   if(a[u] > 2)work(a[u]-1);
50  }
51  printf("%d\n",ANS);
52  return 0;
53 }
54 /*<比较大小不需要强制类型转换而min需要*/
View Code

反思:min里面的两个数字类型必须相同,但<两端数字类型不一定相同。这种利用随机化解决问题都有一个条件,即在时间复杂度内可进行的操作数大于其期望操作数,该题期望操作数为2,而时间复杂度允许的操作数在20左右,这就会使得随机化算法有了意义。

posted @ 2020-03-04 22:30  hyghb  阅读(205)  评论(0编辑  收藏  举报