Educational Codeforces Round 117 (Rated for Div. 2)

时间:2024-01-21 16:04:08

Educational Codeforces Round 117 (Rated for Div. 2)

A. Distance

https://codeforces.com/contest/1612/problem/A

题目给出的条件是Educational Codeforces Round 117 (Rated for Div. 2)Educational Codeforces Round 117 (Rated for Div. 2)

距离为曼哈顿距离,而曼哈顿距离等价于步长。

由题目的一半条件,可以得到步长和为AB步长,各自步长为AB步长的一半。

所以显然可以推出:

\[1.如果和为奇数则,不存在\\
2.如果都为偶数,显然只需要取步长一半即可\\
\]
//原始代码
#include<bits/stdc++.h>
using namespace std;
int t;
int main()
{
scanf("%d",&t);
while(t--)
{
int x,y;
scanf("%d%d",&x,&y);
if((x+y)%2!=0) printf("-1 -1\n");
else if(x%2==0&&y%2==0) printf("%d %d\n",x/2,y/2);
else if(x%2!=0&&y%2!=0)
{
if(x>y) printf("%d %d\n",(x+y>>1)-y,y);
else printf("%d %d\n",x,(x+y>>1)-x);
}
}
return 0;
}
//update
#include<bits/stdc++.h>
using namespace std;
int t;
int main()
{
scanf("%d",&t);
while(t--)
{
int x,y;
scanf("%d%d",&x,&y);
int p=x+y,q=x/2;
if(p%2!=0) printf("-1 -1\n");
else printf("%d %d\n",q,p/2-q);
}
return 0;
}

B. Special Permutation

https://codeforces.com/contest/1612/problem/B

题目为1-n的序列,构造长度为n/2的双序列,保证a在且为左半边最小,b在且为右半边最大

那么显然我们可以这样构造

a放在最前面,然后使得左半边最大。同理右边

#include<bits/stdc++.h>
using namespace std;
int cnt,t,n,a,b;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&a,&b);
int t=n/2;
if(a==t+1&&b==t-1) {for(int i=a;i<=n;i++) printf("%d ",i);for(int i=b;i>=2;i--) printf("%d ",i);printf("1\n");}
else if(a>t||b<t) puts("-1");
else {for(int i=n,cnt=0;cnt==t;i--) if(i!=b) printf("%d%c",i,(++cnt==t?'\n':' '));for(int i=1,cnt=0;cnt==t;i++) if(i!=a) printf("%d%c",i,(++cnt==t:'\ ':' '));}
}

C. Chat Ban

https://codeforces.com/contest/1612/problem/C

题目是一个三角形结构,加一个等差数列,要求第一个超过的列数是多少。

直接二分即可。

这是一个非常经典而又明显的二分题。二分题非常重要的是两个部分,一个是二分性,另外一个是check函数。这里cite一下tutorial。这句话讲的很好。

If we get banned after yy messages, we also get banned after y+1y+1, y+2y+2 and so on messages (and vice versa, if we don't get banned after yy messages, we also don't get banned after y−1y−1, y−2y−2 and so on messages).

三角形的结构,分为两部分,一部分是y<=k,这一部分直接用等差数列求和公式,另外一部分y>k,这一部分需要用一半加上等差的求和

最后得到公式

\[y<=k \ \ \ cnt=\frac{n(n+1)}{2}
\\
y>k \ \ \ cnt =cnt(k)+cnt(k-1)-cnt(2k-1-y)剩下的部分
\]

二分找到第一个>=的即可

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll k,x,all;
int t;
ll get(int x){return x*1ll*(x+1)/2;}
bool check(ll mid)
{
if(mid<=k) return get(mid)>=x;
else return (get(k)+get(k-1)-get(2*k-1-mid))>=x;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%llu%llu",&k,&x);
ll l=0,r=2*k-1;
while(l<r)
{
ll mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%llu\n",r);
}
return 0;
}

D. X-Magic Pair

https://codeforces.com/contest/1612/problem/D

一道简单的数学题

看到操作set =d,很容易发现 这是一个单调递减的过程,必须要把距离赋值给其中的一个,而二者关系无非三种要么大于,要么小于,要么等于。

如果赋给了大的,那么距离将变小,如果赋给了小的,距离有可能变小,也可能变大。如果相等为0,注意一旦出现为0的情况,将直接循环,距离永远等于一个值,而必须赋给其1,则要么0,d,要么就是d,d。

对于前面两种情况,如果是第一种,很显然是不是很像是辗转相除法?只不过这里是更相减损。所以如果大的减去小的的过程中出现了x,那么就成立。那么等价于(b-x)%a==0

而对于第二种情况,如果赋值给小的,那么第二次的距离,将会变成之前a,b的其中一个,那么必须将其赋值给其中一个,则要么变回原样,要么变成情况1的情况。

所以综上所述:只需要按照辗转相除法的过程模拟一下即可,只要在单调下降的过程出现即可。同时因为一旦为0,那么一定会出现循环。此时即可退出。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b,x;
int T;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld%lld",&a,&b,&x);
bool flag=false;
if(a>b) swap(a,b);
if(a==x||b==x) {puts("YES");continue;}
while(a!=0&&b!=0)
{
if((b-x)%a==0&&(b-x)>=0){puts("YES");flag=true;break;}
else
{
ll tmp=a;
a=b%a,b=tmp;
}
}
if(!flag) puts("NO");
}
return 0;
}

E. Messages

https://codeforces.com/contest/1612/problem/E

题目大概是说有n个学生,每个人读k[i]条信,然后要读第m[i]封信,你可以把一堆信去pin一下。求期望。这一道题让我见识了概率题的新的写法。

\[E(x)=F_1+F_2+...+F_n,F_i为第i个人拿到信的情况\\
我们set \{c1,c2,c3,c4...\}为我们pin的信件。\\
F[i]=\begin{cases}
0 & if \ m[i] \notin C\\
1 & if \ m[i]\in C \ and \ k[i]<=t\\
\frac{k[i]}{t} & if m[i]\ \in \ C \ and \ k[i]>t\\
\end{cases}
\]

t很小,只有20,而且对于大于20的部分,是相同的。

\[贪心的正确性:因为每封信对应的是人的情况,且人与人是分开的,所以选择尽可能大的则是正确的。
\]
\[proof:因为最多只会看到20封信, 所以对此他们的和是相同的,还是从大到小选,还是那20个数,但是有些原来是1的成分会因此而变小。
\]

枚举即可。

将其从大到小排序,每一个的值,然后依次往下选即可。将所有的选择比较一下即可。

时间复杂度O(n^2logn)

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N=2e5+52,K=20;
typedef long long ll;
typedef pair<int,int> PII;
int m[N],k[N],n;
bool frac_greater(pair<int, int> a, pair<int, int> b)//比较方式
{
return a.first * b.second > a.second * b.first;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d %d",&m[i],&k[i]);//每个人的情况,第几封信,读k封
vector<int> ans1;//储存每个人的情况
PII ans2={0,1};//储存结果
for(int i=1;i<=K;i++)//因为最多20,所以多余20计算会更小,多余20等于20
{
vector<int> score(N);//开N位,每个位为0
for(int j=0;j<n;j++) score[m[j]]+=min(i,k[j]);//得到每封信的情况如果选择这封信。(如果选了,且小则为1,否则则是k[j],没选则为0)
vector<PII > val;//桶排序
for(int j=0;j<N;j++) val.push_back({score[j],j});
sort(val.rbegin(),val.rend());
PII cur_ans={0,i};
vector<int> lt;
for(int j=0;j<i;j++)cur_ans.x+=val[j].x,lt.push_back(val[j].y);
if(frac_greater(cur_ans,ans2)) ans2=cur_ans,ans1=lt;
}
printf("%d\n",ans1.size());
for(auto x:ans1) cout<<x<<" ";
puts("");
return 0;
}

F. Armor and Weapons

https://codeforces.com/contest/1612/problem/F

F题是一道贪心题,大概是有两种东西,然后通过某种条件到达目标点。

第一反应:贪心,最短路(不知道为什么发现好多好多题目都可以直接挂到图论上,或者间接挂到图论上,最近的比赛看到了好多),DP最优解。

再一看,边权为1,BFS。

然后去看了一眼tutorial,好家伙就是这样。

直接看了一下唯一没能想到的是证明优化。

如果直接的BFS会是nm,这无法接受。

\[我们每次要么兑换 ,一定是兑换最大的,因为如果我们为了凑组合,而选择小的,那么这个更新的会无效甚至更差,因为相当于绕远路获得+1.\\
然后在每一层里面如果x,y小于等于另一组,那么一定是无效的。
\\
那么再原有BFS的基础上加上修改,类似于BFS的剪枝,如果小于等于,也即产生了冗余,那么我们将不用BFS.
\]
#include<bits/stdc++.h>

using namespace std;

int n, m;

#define x first
#define y second typedef pair<int, int> comb; comb norm(const comb& a)
{
return make_pair(min(a.x, n), min(a.y, m));
} bool good(const comb& a)
{
return a.x == n || a.y == m;
} bool comp(const comb& a, const comb& b)
{
if(a.x != b.x)
return a.x > b.x;
return a.y > b.y;
} int main()
{
scanf("%d %d", &n, &m);
int v;
scanf("%d", &v);
set<comb> s;
for(int i = 0; i < v; i++)
{
int x, y;
scanf("%d %d", &x, &y);
s.insert(make_pair(x, y));
}
int steps = 0;
vector<comb> cur;
cur.push_back(make_pair(1, 1));
while(true)
{
if(cur[0] == make_pair(n, m))
break;
vector<comb> ncur;
for(auto x : cur)
{
int sum = x.x + x.y;
if(s.count(x))
sum++;
comb z = x;
z.x = sum;
ncur.push_back(norm(z));
z = x;
z.y = sum;
ncur.push_back(norm(z));
}
sort(ncur.begin(), ncur.end(), comp);
int mx = 0;
vector<comb> ncur2;
for(auto x : ncur)
{
if(x.y <= mx) continue;
mx = max(mx, x.y);
ncur2.push_back(x);
}
cur = ncur2;
steps++;
}
printf("%d\n", steps);
} #include <bits/stdc++.h>
using namespace std;
const int LOG = 30;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
int q;
cin >> q;
vector<int> a(q), b(q);
for (int i = 0; i < q; i++){
cin >> a[i] >> b[i];
a[i]--;
b[i]--;
}
if (n > m){
swap(n, m);
for (int i = 0; i < q; i++){
swap(a[i], b[i]);
}
}
set<pair<int, int>> st;
for (int i = 0; i < q; i++){
st.insert(make_pair(a[i], b[i]));
}
vector<int> dp(n, -1);
dp[0] = 0;
int ans = 0;
while (true){
ans++;
vector<int> dp2(n, -1);
for (int i = 0; i < n; i++){
if (dp[i] != -1){
if (st.count(make_pair(i, dp[i])) == 0){
int x = min(i + dp[i] + 1, m - 1);
dp2[i] = max(dp2[i], x);
int y = min(i + dp[i] + 1, n - 1);
dp2[y] = max(dp2[y], dp[i]);
} else {
int x = min(i + dp[i] + 2, m - 1);
dp2[i] = max(dp2[i], x);
int y = min(i + dp[i] + 2, n - 1);
dp2[y] = max(dp2[y], dp[i]);
}
}
}
if (dp2[n - 1] == m - 1){
cout << ans << endl;
break;
}
swap(dp, dp2);
}
} #include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
set<pair<int,int> > st;
int n,m,q;
inline int query(int u,int v)
{
return u+v+(st.find(make_pair(u,v))!=st.end());
}
int G(int x,int y)
{
x=min(x,n),y=min(y,m);
if(x==n&&y==m) return 0;
int q=query(x,y);
if(x==n) return G(x,q)+1;
if(y==m) return G(q,y)+1;
if(x>y) return G(x,q)+1;
return G(q,y)+1;
}
int F(int x,int y)
{
x=min(x,n),y=min(y,m);
if(x==n&&y==m) return 0;
int q=query(x,y),ans=0;
if(x==n) return F(x,q)+1;
if(y==m) return F(q,y)+1;
if(x>y)
{
ans=F(x,q);
if(x-y<=5) ans=min(ans,F(q,y));
else ans=min(ans,G(q,y));
return ans+1;
}
else
{
ans=F(q,y);
if(y-x<=5) ans=min(ans,F(x,q));
else ans=min(ans,G(x,q));
return ans+1;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=q;i++)
{
int u,v;
scanf("%d%d",&u,&v);
st.insert(make_pair(u,v));
}
printf("%d\n",F(1,1));
return 0;
}

G. Max Sum Array

https://codeforces.com/contest/1612/problem/G

考虑最外面的两个的贡献。每次做完一组切掉前后缀,继续做。

下面是一些证明

\[最外面两个放数目最多的。\\
贪心微扰法+反证法证明:\\
c_{e1}的贡献为(c_{e1}-1)*(n-1) 任意取一个点, i-1+n-i=n-1.一共有c_{e1}个点则最终结果为(c_{e1}-1)*(n-1)\\
设c_{ek}为第一个大于c_{e1}的,c_{ek}的贡献将增(c_{ek}-1)*(p-1),因为前面没有和他相同的,而c_{e1}分为两部分,一部分是\\
(p,n]---dec1=c_{e1}[p+1,n]⋅(p−1) 另一部分是(1,p),由于是从左边到右边,这里面会产生相对关系的变化\\但是我们仍然可以通过估计来得到结果,dec2≤c_{e1}[2,p−1]⋅(p−1) .这里一方面是放缩,另一方面是为了合并方便。\\
inc−dec≥(p−1)⋅(c_{ek}−c_{e1})>0,则证明了我们的swap选择是更优的。所以最大的一定要放在外面\\
其实从直觉上我们也可以得到这样的结论,对于任意的一组点,我们一定想要把它的贡献最大化,而牵扯到的数字越多的点\\他的变动就会带动整体的变动越大,所以我们肯定要让这样的点最先被处理,同时处理的最大。\\
题目中还要求了数量。很容易可以想到排列组合。\\
这促使我们想到,当我们具有多组相同的最大值的时候我们该如何安排。显然他们的任意排列是正确的。
这里我们给出证明\\
set \ l_i,r_i分别为我们所处理的点的左右端点。可以得到contri为 (c_{ei}−1)⋅(r_i−l_i),和为\Sigma_{i=1}^k(c_{ei}−1)⋅(r_i−l_i) =\\ (c_{e1}−1)(∑_{i=1}^kri−∑_{i=1}^kl_i) = (c_{e1}−1)((n−k)k+k(k+1)2−k(k+1)2)=(c_{e1}−1)k(n−k)\\
右边的式子哪里来的?可以用鸽巢原理,因为是左右对称放,且空间大小刚好,那么一定会遍及所有的数。所以求和公式。
\\那么下面就只需要执行这样的步骤即可\\
找到最大值--如果为1,num为0;否则加上贡献,num则乘上他们的排列\\因为左右两边是分开的,且一定是左边一个右边一个,所以num乘上{k!}^2。\\
执行完以后,去掉这两个数,同时更新信息。反复操作。\\
如果最后为1的话,那么直接乘上一次阶乘即可。
\\一定要注意防止整形溢出
\\时间复杂度 O(n+C)
\]
#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=int(l);i<int(r);i++)
#define rep2(i,l,r) for(int i=int(l);i>int(r);i--)
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=1e6+52,mod=1e9+7;
template<class A, class B> ostream& operator <<(ostream& out, const pair<A, B> &p) {
return out << "(" << p.x << " " << p.y << ")";
}
template<class A> ostream& operator <<(ostream& out, const vector<A> &v) {
rep(i, 0, sz(v)) {
if(i) out << " ";
out << v[i];
}
return out;
}
ll m;
ll fact[N],cnt[N];
ll total,ans_sum,ans_num=1;
void init()
{
fact[0]=1;
for(int i=1;i<N;i++) fact[i]=fact[i-1]*1ll%mod*i%mod;
}
int main()
{
init();
scanf("%lld",&m);
rep(i,0,m)
{
ll x;scanf("%lld",&x);
cnt[x]++;total+=x;
}
rep2(i,N-50,1)//整形溢出
{
ans_num=(ans_num%mod*(fact[cnt[i]]%mod*fact[cnt[i]]%mod)%mod)%mod;
ans_sum=(ans_sum +(((i - 1)*cnt[i]%mod)*((total - cnt[i]) % mod)%mod))%mod;
total-=2*cnt[i],cnt[i-2]+=cnt[i];
}
ans_num=(ans_num*fact[cnt[1]])%mod;
cout<<ans_sum<<" "<<ans_num<<'\n';
return 0;
}

这里提供一下大佬的想法

Here is a slightly different approach for problem G which I think is easier. (Sorry if my explanation is not good.) It would seem that many other people have this approach too.

First, let's ask ourselves, given an array, how can we find the total sum of distances between all pairs of equal elements? For each element, we will need to add to the answer the sum of absolute values between each pair of indexes where it exists. This is a very well known problem, and can be easily understood by looking at an example.

Let's say an element exists at the indices [1,4,5,6,8][1,4,5,6,8]. Then, we will need to add (4−1)+(5−1)+(6−1)+(8−1)+(5−4)+(6−4)+(8−4)+(6−5)+(8−5)+(8−6)(4−1)+(5−1)+(6−1)+(8−1)+(5−4)+(6−4)+(8−4)+(6−5)+(8−5)+(8−6) to the answer. Overall, 1 has been subtracted 4 times, 4 has been subtracted twice, 5 does not contribute towards the sum, 6 is added twice, and 8 is added 4 times. So we will add to the sum (−4)∗1+(−2)∗4+(0)∗5+(2)∗6+(4)∗8(−4)∗1+(−2)∗4+(0)∗5+(2)∗6+(4)∗8. In general, for a sorted array [p1,p2,…,px][p1,p2,…,px], we will add to the answer p1∗(1−x)+p2∗(3−x)+p3∗(5−x)+...+px∗(x−1)p1∗(1−x)+p2∗(3−x)+p3∗(5−x)+...+px∗(x−1). We can think of this as assigning multiplying each index by a coefficient and finding the total sum of indexes, such that the coefficients assigned to indexes with the same element are a [1−x,3−x,…,x−1][1−x,3−x,…,x−1] in ascending order.

We can now move on to maximising the answer. We will generate a coefficient array. For each cici, we will add the elements [1−ci,3−ci,…,ci−1][1−ci,3−ci,…,ci−1] to the coefficient array. Then, we want to obtain a permutation of this coefficient array [p1,p2,…,pn][p1,p2,…,pn] such that ∑ni=1ipi∑i=1nipi is maximised. By the rearrangement theorem, this sum is maximised when pp is sorted, and it is easy to see that such a permutation is possible.

For more clarity, consider the case where c=[3,3,2]c=[3,3,2]. We will generate the coefficient array [−2,0,2]+[−2,0,2]+[−1,1]=[−2,−2,−1,0,0,1,2,2][−2,0,2]+[−2,0,2]+[−1,1]=[−2,−2,−1,0,0,1,2,2]. Then the maximum answer will be (−2)∗0+(−2)∗1+(−1)∗2+(0)∗3+(0)∗4+(1)∗5+(2)∗6+(2)∗7=27(−2)∗0+(−2)∗1+(−1)∗2+(0)∗3+(0)∗4+(1)∗5+(2)∗6+(2)∗7=27, and this is achievable for example by choosing a=[1,2,3,1,2,3,1,2]a=[1,2,3,1,2,3,1,2].

Now, how do we do this fast? Instead of actually generating the coefficient array, we will simply create a frequency map storing how many times each element exists in the coefficient array. We can create this map quickly using a difference array (or you can visualise this as a sweepline). We will then iterate through this map in ascending order. For each element ee which occurs numnum times in the coefficient array, we will assign ee as the coefficient of the numnum lowest indexes which we haven't assigned yet, and increment our answer by (e∗e∗ sum of chosen indexes). Remember that of the distinct elements in aa, exactly numnum of these elements will have contributed ee to the coefficient array, so these indexes will correspond to some permutation of these numnum elements in aa. We will therefore multiply the number of possible arrays by num!num!, as this is the number of permutation of these numnum elements in aa.

See https://codeforces.com/contest/1612/submission/136599117 for implementation details. If an array is used instead of a map, the overall complexity of this algorithm is O(m+max(ci))O(m+max(ci)).