2020 Jiangsu Collegiate Programming Contest(C,D,H,J)

oj: CodeForces

C. Cats

oj: CodeForces

题意

让你求出满足条件的任意一个序列并输出。
条件:两个相等的值之间至少存在一个小于这个值的其他值。
eg:\(\{1,2,3\}\)符合条件,\(\{3,4,3\}\)不符合条件。

题解

首先肯定只能有一个\(1\),我们假设这个\(1\)在最左边。
然后考虑\(2\)的位置,\(2\)只能在\(1\)的右边。
\(3\)只能在\(2\)的两边,之后的类比\(3\)的情况。
构造出来的序列就是这样的:
\(\{1\}\)
\(\{1,2\}\)
\(\{1,3,2,3\}\)
\(\{1,4,3,4,2,4,3,4\}\)
\(\{1,5,4,5,3,5,4,5,2,5,4,5,3,5,4,5\}\)
我们找规律发现,每增加一个数\(i\)可以增加\(2^{i-2}\)个位置,肯定可以找到满足最大长度\(100000\)的序列(等比数列前\(n\)项和可以证明)。
对于\(n\)不是\(2\)的幂次的情况,我们可以找到比\(n\)大的最小的\(2\)的幂次构造出来的数组,输出前\(n\)项即可。
剩下就是模拟构造出来这个数组的过程了。
这里假设数组中最大的那个数是\(num\)。
可以发现每个\(num\)间距为\(2\),每个\(num-1\)间距为\(4\),每个\(num-2\)间距为\(8\),因此我们求出\(num\),倒序枚举要放的数和开始放这个的数的起始位置,并同时维护间距即可。
注意\(1\)和\(2\)的特殊情况。

代码

#include <bits/stdc++.h>
#define PI atan(1.0)*4
#define rp(i,s,t) for (register int i = (s); i <= (t); i++)
#define RP(i,t,s) for (register int i = (t); i >= (s); i--)
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pil pair<int,ll>
#define m_p make_pair
#define p_b push_back
#define ins insert
#define era erase
#define INF 0x3f3f3f3f
#define inf 0x3f3f3f3f3f3f3f3f
#define dg if(debug)
#define pY puts("YES")
#define pN puts("NO")
#define outval(a) cout << "Debuging...|" << #a << ": " << a << "\n";
#define outval2(a,b) cout << "Debuging...|" << #a << ": " << a <<"\t"<< #b << ": " << b << "\n";
#define outval3(a,b,c) cout << "Debuging...|" << #a << ": " << a <<"\t"<< #b << ": " << b <<"\t"<< #c << ": " << c << "\n";
using namespace std;
int debug = 0;
ll gcd(ll a,ll b){
    return b?gcd(b,a%b):a;
}
ll lcm(ll a,ll b){
    return a/gcd(a,b)*b;
}
inline int read(){
    int s=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s*f;
}
const int N = 1e6+7;
int a[N];
void solve(){
    int n=read();
    if(n==1){
        puts("1");
        return ;
    }
    int num=0;
    while((1<<num)<n) num++;
    int nn=num+1;
    rp(i,1,(1<<num)) a[i]=0;
    // outval3(n,num,nn);
    a[1]=1;
    int delta=2;
    while(delta<=(1<<num)){
        int id=0;
        rp(i,1,(1<<num)){
            if(!a[i]){
                id=i;
                break;
            }
        }
        if(!id) break;
        while(id<=(1<<num)){
            a[id]=nn;
            id+=delta;
        }
        nn--;
        delta*=2;
    }
    rp(i,1,n) cout<<a[i]<<(i==n?"\n":" ");
}
int main(){
    //ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifdef ONLINE_JUDGE
#else
    freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    // debug = 1;
#endif
    time_t beg, end;
    if(debug) beg = clock();

    int T=1;
    while(T--) solve();

    if(debug) {
        end = clock();
        printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
    }
    return 0;
}

D. Delete Prime

oj: CodeForces

题意

定义一个生成序列的流程:

  1. 根据 \(n\) 生成一个 \(1,2,...,n\) 数组。
  2. 取出序列里第 \(1\) 和第素数位置的元素,保持其相对位置不变加入到 \(D\) 数组的末尾。
  3. 重复 \(2\) 过程直到原数组为空。

例如 \(n=6\) 时,数组 \(D\) 为 \([1,2,3,5,4,6]\) 。

有若干次提问,每次提问会给出 \(3\) 个整数: \(t,n,k\) 。其中 \(t\) 表示提问类型:

  1. 给定 \(n\) 和 \(k\) ,求出 \(x\) 满足 \(D[x]=k\) 。
  2. 给定 \(n\) 和 \(k\) ,求出 \(x\) 满足 \(D[k]=x\) 。

题解

测试了一下, \(n\) 为 \(1000,000\) 时,需要 \(80\) 轮迭代求出 \(D\) 。把每次迭代取出的数单独存为一个数组,那么这些数组都是有序的。

然后根据给定的 \(n\) 和 \(k\) 从第一个数组到最后一个数组依次二分查找即可。

  1. 给定了值,求索引。按照迭代顺序依次枚举数组,直接在此数组里二分查找 \(k\) ,如果找不到就维护一个 和,和 保存的是此数组中小于等于 \(n\) 的数的个数。如果找到了,就把 \(k\) 在此数组中的位置加上维护的 和 就是 \(k\) 在 \(D\) 数组中的位置。
  2. 给定了位置,求值。仿照情况 \(1\) 的思路,维护一个 和 保存小于等于 \(n\) 的元素的个数,一旦个数之和大于等于 \(k\) ,就说明 \(D\) 中第 \(k\) 个元素在当前我们枚举的数组里。直接输出即可。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 1000000;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int a[maxn + 1];
int used[maxn + 1];
vector<int> b[81];
int cnt;

vector<int> arr;
int vis[1000006];
void doit(int maxnum) {
    for(int i = 2; i <= maxnum; ++i) {
        if(!vis[i]) arr.push_back(i);
        for(int j = 0; j < arr.size() && arr[j] * i <= maxnum; ++j) {
            vis[arr[j] * i] = 1;
            if(i % arr[j] == 0) break;
        }
    }
}

int findl(int de, int val) {
    return lower_bound(b[de].begin(), b[de].end(), val) - b[de].begin();
}

int findu(int de, int val) {
    return upper_bound(b[de].begin(), b[de].end(), val) - b[de].begin();
}

void sol() {
    int t = read(), n = read(), k = read();
    if(t == 1) {
        int num = 0;
        _for(i, cnt) {
            int p = findl(i, k);
            if(p < b[i].size() && b[i][p] == k) {
                printf("%d\n", num + p + 1);
                return;
            }
            else num += findu(i, n);
        }
    }
    else {
        _for(i, cnt) {
            int p = findu(i, n);
            if(p >= k) {
                printf("%d\n", b[i][k - 1]);
                return;
            }
            else k -= p;
        }
    }
}

int main() {
    doit(1000000);
    _rep(i, 1, maxn) a[i] = i;
    cnt = 0;
    int n = 1000000;
    while(n) {
        _rep(i, 1, n) used[i] = 0;
        used[1] = 1;
        b[cnt].push_back(a[1]);
        for(int i = 0; i < arr.size() && arr[i] <= n; ++i) {
            used[arr[i]] = 1;
            b[cnt].push_back(a[arr[i]]);
        }
        int tn = 0;
        _rep(i, 1, n) if(!used[i]) a[++tn] = a[i];
        n = tn;
        ++cnt;
    }
    int T = read();
    _for(i, T) {
        sol();
    }
    return 0;
}

H. Happy Morse Code

oj: CodeForces

题意

给定一组密钥 \(s\) 和一串密码 \(t\) ,求出是否可以根据这组密钥唯一求解密码,如果不能求解就输出nonono,如果能唯一求解就输出happymorsecode,如果有多种求解方式就输出puppymousecat和求解方法数,答案对 \(128\) 取余。

题解

动态规划。

定义 \(dp[i]\) 为密码的字串 \(t[i:n]\) 的求解方法数。
\(ln[j]\) 为第 \(j\) 个密钥的长度。

边界值: \(dp[n]=1\)

状态转移:\(dp[i] = \sum\limits_{j=0}^{m-1}{dp[i + ln[j]]}\) \((t[i:i+len(s[j])]=s[j])\)。

注意,我们需要根据 \(dp[0]\) 的值来判断答案有多少种求解方式,所以 \(i\) 中间的计算过程不能直接对 \(128\) 取余,否则我们不知道最后的答案 \(0\) 是因为没有解还是取余后变成了 \(0\) 。所以稳妥的做法是如果大于 \(128\) 就先减去 \(128\) ,再对 \(128\) 取余,再加上 \(128\) 。之后答案的值就能维持在大于 \(128\) 但不超过 \(256\) 的水平。最后输出的时候再对 \(128\) 取余。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 100005;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

LL dp[maxn];
int n, m;
char s[26][11];
char t[maxn];
int ln[maxn];
int fl;

void init() {
    fl = 0;
    _for(i, n + 1) dp[i] = 0;
}

int che(int i, int p) {
    _for(j, ln[i]) {
        if(t[p + j] != s[i][j]) return 0;
    }
    return 1;
}

void sol() {
    init();
    _for(i, m) {
        scanf("%s", s[i]);
        scanf("%s", s[i]);
        ln[i] = strlen(s[i]);
    }
    scanf("%s", t);
    dp[n] = 1;
    for(int i = n - 1; i >= 0; --i) {
        _for(j, m) {
            if(i + ln[j] > n) continue;
            if(che(j, i)) {
                dp[i] += dp[i + ln[j]];
                if(dp[i] > 128) {
                    dp[i] = (dp[i] - 128) % 128 + 128;
                }
            }
        }
    }
    if(dp[0] == 0) printf("nonono\n");
    else if(dp[0] == 1) printf("happymorsecode\n");
    else printf("puppymousecat %lld\n", dp[0] % 128);
}

int main() {
    int T = read();
    _for(i, T) {
        n = read(), m = read();
        sol();
    }
    return 0;
}

J. Just Multiplicative Inverse

oj: CodeForces

题意

给定一个函数,每回合给出一个 \(p\) ,求出此函数的调用次数。

题解

观察后发现函数再调用过程中 \(p\) 不变, \(x\) 持续变小。所以可以按 \(x\) 从小到大计算,较大的 \(x\) 可以利用到较小的 \(x\) 的结果,从而避免多余计算,进行记忆化搜索。

代码

#include <bits/stdc++.h>
#define _for(i, a) for (int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for (int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;
const int maxn = 1000005;

inline LL read() {
    LL x(0), f(1); char ch(getchar());
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}
LL p, ans;
LL dp[maxn];
inline LL dfs(LL x, LL p, int dep) {
    if(dp[x] != -1) return dp[x];
    if(x <= 1) return 1;
    return dp[x] = dfs(p % x, p, dep + 1) + 1;
}
void init() {
    ans = 0;
    _rep(i, 1, p) dp[i] = -1;
}
void sol() {
    init();
    _rep(i, 1, p - 1) ans += dfs(i, p, 1);
}
int main() {
    int T = read();
    _for(i, T) {
        p = read();
        sol();
        printf("%.10f\n", 1.0 * ans / (p - 1));
    }
    return 0;
}
posted @ 2021-02-28 11:14  指尖跳动的电光  阅读(163)  评论(0)    收藏  举报