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 /**/
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 /**/
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 /**/
反思:最开始我考虑将所有数对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 /**/
反思:注意一下交互题的写法吧。每次输出给系统询问的时候,需要加上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 /**/
反思: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需要*/
反思:min里面的两个数字类型必须相同,但<两端数字类型不一定相同。这种利用随机化解决问题都有一个条件,即在时间复杂度内可进行的操作数大于其期望操作数,该题期望操作数为2,而时间复杂度允许的操作数在20左右,这就会使得随机化算法有了意义。