Codeforces Global Round 24(A-D)

A.Doremy's Paint

贪心一下就知道,如果每个数都不一样的话l和r怎么选答案都是一样的,那么直接选最大范围尽可能选中更多重复的数字即可

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];

void solve(){
    int n;
    cin>>n;
    for(int i =1;i<=n;i++)
        cin>>a[i];
    cout<<1<<" "<<n<<endl;
}
int main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t = 1;
    cin>>t;
    while(t--){
        solve();
    }
}

B. Doremy's Perfect Math Class

找到数列的公共gcd = g,答案就是最大的值/g。

粗略证明的话就是假设有两个数a和b(a>b),其gcd = g,那么a可以表示成 c1g b可以表示成 c2g ,无论怎么减都是g的倍数。同时c1,c2,...cn的gcd为1,那么自然会减出来1-c最大值。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];

int GCD(int a, int b) {
    return b ? GCD(b, a % b) : a;
}

void solve(){
    int n;
    cin>>n;
    for(int i =1;i<=n;i++)
        cin>>a[i];
    int now = a[1];
    int mx = 0;
    for(int i = 2;i<=n;i++){
        now = GCD(now,a[i]);
        mx = max(mx,a[i]);
    }
    cout<<mx/now<<endl;

}
int main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t = 1;
    cin>>t;
    while(t--){
        solve();
    }
}

C. Doremy's City Construction

由题意简单可知,一个点能连的边一共只有三种可能

  1. 全连比自己大的边
  2. 全连比自己小的边
  3. 只连一条边,这条边的权值和自己相等

同时确定一个点连边后又能确定其他点的性质

假设两个点a和b,a连b是因为b是大于a的边,连上以后b就绑定了性质2

那么对所有点值进行排序后,如果考虑最优的话一定是只用性质1和性质2,那么就相当于把点分为两部分,然后每个点会连另一部分的所有点。性质3特判一下。

然后我们会尽量把点均分,因为假设左边部分点的个数是a1,右边部分点的个数是a2, a1+a2=n ,边的个数 = a1∗a2 ,当a1a2尽量相等的时候积最大,然后会有例如1 2 4 4 4 4这样的数列情况,如果还是均分就会出现4和4相连的性质3情况,这时枚举把4划分到右边的情况和划分到左边的情况,取最大值即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];
map<int,int>num,pre;

int GCD(int a, int b) {
    return b ? GCD(b, a % b) : a;
}

void solve(){
    ll n;
    cin>>n;
    for(int i =1;i<=n;i++)
        cin>>a[i];
    int ok = 0;
    for(int i =1;i<=n;i++){
        if(a[i]!=a[1]) {
            ok = 1;
            break;
        }
    }
    if(!ok){
        cout<<n/2<<endl;
        return;
    }
    sort(a+1,a+n+1);
    ll mid = n/2;
    ll i = mid,j = mid;
    while(i<=n&&a[i]==a[mid])
        i++;
    i--;
    while(j>=1&&a[j]==a[mid])
        j--;
    j++;
    ll ans = max(1LL*(n-i)*i,1LL*(j-1)*(n-j+1));
    cout<<ans<<endl;
}
//对同一个点而言,要么只能都连比其小的,要么只能都连比其大的,相等只能连一个,简单无向图不能自环不能重边
int main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int t = 1;
    cin>>t;
    while(t--){
        solve();
    }
}

D. Doremy's Pegging Game

首先我们去考虑临界情况,即去掉一个点就无法继续取下去了

如图所示,如果我们考虑到每一种这样的临界情况,我们对于其他已经去掉的点进行全排列即可获得。

那么我们想要怎么获得这样的临界情况,我们不难发现,假设是图2的正6边形,我们还有这样的情况也是临界情况

也就是说 临界情况我们可以看成是一条较长的段和一个点组成(段中的点不一定是连续的),如图4所示

同时对于段而言,不是每个不在段上的点都能构造出临界情况,而是一定要在关于中心对称的段的范围中的点(有点抽象,感觉需要抽象理解一下,证明不太会qaq)如图5所示,橙色点是中心钉子,绿色弧段是对称段,绿色点不在绿色弧段上,不能被选取。

图5

那么我们枚举这条段的长度i,然后枚举在长度i下的这个段的取点的方案数x,再乘以对于段而言,能构建临界状态点的方案数y,然后再乘以其他点的全排列即可。最后我们要在前面乘以一个n,因为我们枚举的是段长度,再枚举每个点作为起点。

n*\sum_{i=2}^{(n+1)/2}\sum_{j=2}^{i} C^{i-2}_{j-2}*\left( i-n\%2 \right)*P\left(n-j-1 \right)

其他点的全排列是因为肯定是在进入临界状态前被选取的,同时也不会因为随便取进入另一个临界状态,所以可以任意排列。

i−n%2 是对称弧段点的个数,对于奇数点的时候会少一个

同时我们代入这个式子后发现,对于偶数情况存在只需要两个点的临界情况,即弧段长度为1的情况。但这种情况我们只要确定一个点就可以确定另一个点,其他点全排列即可。对于答案的贡献是

n∗P(n−2)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn =2e6+10;

ll n,mid;
ll mod;
ll fac[maxn], fnv[maxn];

ll qpow(ll a, ll p) {
    ll res = 1;
    while (p) {
        if (p & 1) {
            res = res * a % mod;
        }
        a = a * a % mod;
        p >>= 1;
    }
    return res;
}

void init(int n) {
    fnv[0] = fnv[1] = fac[0] = 1;
    for (int i = 1; i <= n; i++) {
        fac[i] = fac[i - 1] * i % mod;
    }
    fnv[n] = qpow(fac[n], mod - 2);
    for (int i = n; i >= 1; i--) {
        fnv[i - 1] = fnv[i] * i % mod;
    }
}

ll C(int n, int m) {
    if (n < m || m < 0) return 0;
	return fac[n] * fnv[n - m] % mod * fnv[m] % mod;
}

ll P(int n, int m) {
    if (n < m || m < 0) return 0;
	return fac[n] * fnv[n - m] % mod;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> mod;
    init(n + 5);
    ll ans = 0;
    if (n % 2 == 0) {
        ans = (ans + fac[n - 2]) % mod;
    }
    for (int i = 2; i <= (n + 1) / 2; i++) {  //枚举区间长度
        for (int j = 2; j <= i; j++) {  //枚举区间含点
            ll x = C(i - 2, j - 2);  //根据i和j确定的选中的点的方案数
            ll y = i-n%2;          //在另一侧的维持临界情况的点的方案数
            ans = (ans + x * y % mod * fac[n - j - 1] % mod) % mod;   //剩下的数的全排列
            //cout<<i<<" "<<j<<" "<<ans<<endl;
        }
    }
    ans = ans * n % mod;  //枚举起点
    cout << ans << '\n';
    return 0;
}
毛金爹代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值