[CodeForces 285D Permutation Sum] (搜索)

本文深入探讨了代码生成技术及其在组合算法中的应用,详细分析了如何利用算法解决特定问题,并通过实例展示了代码生成的过程与技巧。文章还讨论了算法优化策略,旨在提升代码效率与可读性。

题目链接:http://codeforces.com/problemset/problem/285/D


题目大意:

有序列 a,b。其中长度均为N,而a1,a2,a3.....an各不相同,且都属于[1, n]。b也是。

现在有一个操作,ci = ((ai - 1 + bi - 1) mod n) + 1 (1 ≤ i ≤ n). 从而生成一个新的序列C。且使得C也符合上述序列的要求。

问a,b有多少种不同的选择,当a!=b时,(a,b)(b,a)为两种选择。


解题思路:

只能够想到n * n!的方法,而且通过打表可以偶数时为0,但奇数最大为15,15!* 15 太大了。

网上看到一个复杂度 为(2^n) * (2^n) 的搜索。虽然也无法在3s内搜完,但至少能打出表。。= =!


其实只需要找到1,2,3,4,……n有多少种匹配的b序列,之后将答案乘以n!(n的全排列)即可。

对于n,用[0, 1<<n)(即二进制枚举)来表示,然后分为两部分搜索。

前一部分选取n1个数字,然后用(1,2,3,……n1)这些数字作为b序列,后一部分选取n-n1个数,然后用(n2,n3……n)作为b序列(在搜索过程中考虑了排列问题)

两者sum1[j] * sum2[j ^ ((1 << n) - 1]即为该种情况的答案数。

/*
ID: wuqi9395@126.com
PROG:
LANG: C++
*/
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<fstream>
#include<cstring>
#include<ctype.h>
#include<iostream>
#include<algorithm>
#define INF (1<<30)
#define PI acos(-1.0)
#define mem(a, b) memset(a, b, sizeof(a))
#define For(i, n) for (int i = 0; i < n; i++)
const int MOD = 1000000007;
typedef long long ll;
int n;
int n1, n2;
int g1[20], g2[20];
int key;
int cnt1, cnt2;
ll sum1[1 << 17], sum2[1 << 17];

int pan(int i, int k) {
    int sum = 0;
    while(i) {
        sum += i & 1;
        i = i >> 1;
    }
    return sum == k;
}

void dfs1(int s) {
    if(s > n1) {
        sum1[key]++;
        return ;
    }
    for(int i = 0; i < cnt1; i++) {
        int tmp = g1[i];
        if(g1[i] == -1) continue;
        int tmp1 = (tmp + s - 2) % n + 1;
        if(key & ( 1 << (tmp1 - 1) ) ) continue;
        key |= ( 1 << (tmp1 - 1) );
        g1[i] = -1;
        dfs1(s + 1);
        g1[i] = tmp;
        key ^= ( 1 << (tmp1 - 1) );
    }
}
void dfs2(int s) {
    if(s > n) {
        sum2[key]++;
        return ;
    }
    for(int i = 0; i < cnt2; i++) {
        int tmp = g2[i];
        if(g2[i] == -1) continue;
        int tmp1 = (tmp + s - 2) % n + 1;
        if(key & ( 1 << (tmp1 - 1) ) ) continue;
        key |= ( 1 << (tmp1 - 1) );
        g2[i] = -1;
        dfs2(s + 1);
        g2[i] = tmp;
        key ^= ( 1 << (tmp1 - 1) );
    }
}
int main() {
    for(int i = 1; i <= 16; i++) {
        n = i;
        n1 = n / 2;
        n2 = n - n1;
        ll ans = 0;
        for(int i = 0; i <= (1 << n) - 1; i++) {
            if(pan(i, n1) == 0) continue;
            cnt1 = 0;
            cnt2 = 0;
            for(int j = 0; j < n; j++) {
                if( (1 << j)&i ) g1[cnt1++] = j + 1;
                else g2[cnt2++] = j + 1;
            }
            memset(sum1, 0, sizeof(sum1));
            memset(sum2, 0, sizeof(sum2));
            key = 0; // 主要用来记录c的情况...
            dfs1(1);
            key = 0;
            dfs2(n1 + 1);
            for(int j = 0; j <= (1 << n) - 1; j++) {
                int tmp = j ^ ((1 << n) - 1);
                ans = (ans + (sum1[j] * sum2[tmp]) % MOD) % MOD;
            }
        }
        for(int i = 1; i <= n; i++)
            ans = (ans * i) % MOD;
        printf("%I64d,", ans);
    }
    return 0;
}


Another Permutation Problem 是 Codeforces Round 892 (Div. 2) 中的 C 题,属于纯数学方法的思维题。 在解决该问题时,有多种不同的代码实现方式。一种实现方式使用了标准输入输出流和数组,通过多次循环计算得出结果。代码如下: ```cpp #include <iostream> #include <algorithm> #include <set> #include <map> #include <queue> #include <vector> #include <stack> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; const int maxn = 550; long long s[maxn]; int main(){ int t; cin >> t; while(t--){ long long n; cin >> n; long long ans = 0; s[0] = 0; for(long long i = 1;i <= n;i++){ s[i] = s[i-1]+i*i; } for(long long i = 1;i <= n;i++){ long long sum = s[i-1]; long long maxl = 0; long long temp; for(long long j = i;j <= n;j++){ temp = (n-(j-i))*j; sum += temp; if(temp>maxl){ maxl = temp; } } sum -= maxl; if(sum>ans){ ans = sum; } } cout << ans << endl; } return 0; } ``` 还有一种实现方式使用了`bits/stdc++.h`头文件和`vector`容器,通过`reverse`函数翻转数组来计算结果,代码如下: ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; void solve() { int n; cin>>n; vector<int>v(n); iota(v.begin(),v.end(),1); int ans=0,sum=0,mx=0; for(int i=0;i<n;i++){ reverse(v.begin()+i,v.end()); mx=0,sum=0; for(int j=0;j<n;j++){ sum+=v[j]*(j+1); mx=max(mx,v[j]*(j+1)); } ans=max(ans,sum-mx); reverse(v.begin()+i,v.end()); } cout<<ans<<'\n'; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int t; cin>>t; while(t--) solve(); return 0; } ``` 此外,还有一种实现方式是先进行初始化操作,然后通过循环枚举不同长度的翻转区间来计算结果,代码如下: ```cpp #include<iostream> #include<algorithm> using namespace std; //证明:逆序一定是最小的,正序一定是最大的(不删减的情况下) int a[500]; int main(){ int t; cin>>t; while(t--){ int n; cin>>n; for(int i=1;i<=n;i++){ a[i]=i; } int ans=0; for(int len=1;len<=n;len++){ int res=0; int tmp=0; for(int i=0;i<=n-len;i++){ res+=a[i]*i; tmp=max(tmp,a[i]*i); } for(int i=n-len+1,j=n;i<=n;i++,j--){ res+=a[i]*j; tmp=max(tmp,a[i]*j); } res-=tmp; ans=max(ans,res); } cout<<ans<<endl; } } ``` 在赛时做法中,有人采用了官方题解的做法,即枚举`i×j`,不过使用并查集进行维护;而官方题解的维护方式是从`n`倒序枚举到`1`,对于当前数字,找到小于`M/x`的还没被用过的最大的数字,通过维护一个栈,利用`M/x`单调递增的特性,将新增加的可行的数放进栈里,然后弹出栈顶 [^3]。 也可以通过算`n = 1,2,3,4`的情况,发现可以枚举`i`,让`i`前的数顺序排列,从`i`开始逆序排列,进而计算出最大值 [^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值