2019 Multi-University Training Contest 4 部分题解及AC代码

A - AND Minimum Spanning Tree

题意:你有一个完整的图,有N个顶点,从1到N。顶点x和顶点y (1<=x, y<=N, x!=y)之间的边的权值就是x和y的位和(AND)。你现在要求这个图的最小生成树每个点个连向哪个点

题解:为了让权值最小,肯定想让每一位都为0,假设某个数x的最低为的0的位置在i为,那么x & (1<<i) == 0。如果(1<<i)不在N的范围内,那么他至少要有一位AND后为1,那么不如把它和1连边。我们知道lowbit可以求最低为的1,那么按位取反在lowbit就是最低为0的位置

#include <bits/stdc++.h>

using namespace std;

int lowbit(int x){
    return x & (-x);
}

int ans[200005],sum;
int main()
{
    int T;
    cin >> T;
    while (T--){
        int n;
        scanf("%d",&n);
        sum = 0;
        for (int i = 2; i <= n; i++){
            ans[i] = lowbit(~i);
            if (ans[i] > n) ans[i] = 1;
            sum += ans[i] & i;
        }
        printf("%d\n", sum);
        for (int i = 2; i<= n;i++){
            if (i == 2) printf("%d", ans[i]);
            else printf(" %d", ans[i]);
        }
        printf("\n");
    }
    return 0;
}

C - Divide the Stones

题意:你有n堆石子,第i堆石子的重量是i,要求分成k组,是每组的重量相等

题解:如果石子的总重\frac{n \cdot(n+1)}{2}不是k的倍数,则不能分;否则可以分,且每堆n/k个石子

如果\frac{n}{k}是偶数,则从前往后,在从后往前交替放石子就可以了

如果\frac{n}{k}是奇数,则先把前1 ~ 3 \cdot k个石子分成k组,然后\frac{n - 3\cdot k}{k} = \frac{n}{k}-3

1 ~ 3 \cdot k怎么分,假设k = 9, 然后自己找规律吧

19 14 9
20 15 7
21 16 5
22 17 3
23 18 1
24 10 8
25 11 6
26 12 4
27 13 2

#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define FOR(I,S,T) for(int I=(S);I<=(T);I++)
using namespace std;
const int maxn = 1e5 +2;
typedef long long ll;

vector<int> v[maxn];

int main() {
    int T;
    cin >> T;
    while (T--){
        ll n, k;

        scanf("%lld%lld",&n,&k);
        for (int i = 0; i <= n; i++) v[i].clear();
        if (k == 1){
            puts("yes");
            FOR(i,1,n) printf("%d%c", i, i == n?'\n':' ');
            continue;
        }
        if ((n == k) || (((n + 1) * n / 2 % k) != 0)){
            puts("no");
            continue;
        }
        puts("yes");
        if ((n / k) % 2ll== 0){
            ll l = 1, r = n, d = n / k / 2;
            FOR(i, 1, k) FOR(j, 1, d){
                printf("%d %d",l++, r--);
                if (j == d) putchar('\n');
                else putchar(' ');
            }
        }
        else {
            FOR(i, 2 * k +1, 3 * k) v[i-2*k].push_back(i);
            FOR(i, k +1, k * 3 / 2) v[i-k+(k+1)/2].push_back(i);
            FOR(i, k * 3 / 2 + 1, 2 * k) v[i-k*3/2].push_back(i);
            int tmp = k + 1;
            FOR(i, 1, (k + 1)/2) v[i].push_back(tmp - i), tmp--;
            tmp = k * 3 / 2 + 1;
            FOR(i, (k + 1) / 2 + 1, k) v[i].push_back(tmp - i), tmp--;

            int l = 3 * k + 1, r = n, d = (n/k - 3) / 2;
            FOR(i, 1, k) FOR(j, 1, d) v[i].push_back(l++), v[i].push_back(r--);
        }
        for (int i = 1; i <= k; i++){
            for (int j = 0; j < (int)v[i].size(); j++){
                printf("%d", v[i][j]);
                if (j == (int)(v[i].size()) - 1) putchar('\n');
                else putchar(' ');
            }
        }
    }

    return 0;
}

G - Just an Old Puzzle

题意:给出一个数字华容道,问能否还原

题解:可以发现相邻两个方格是可以移动的,所以按照蛇皮型或者环绕行把原数排列下来,因为相邻两个是无法交换的,所以逆序对是奇数数可以还原,否则不能还原

#include <bits/stdc++.h>

using namespace std;

vector<int> v;
int a[4][4];

int main()
{
    int T;
    cin >> T;
    while (T--){
        v.clear();
        for (int i = 0; i <4 ;i++)
            for (int j = 0; j < 4; j++){
                scanf("%d", &a[i][j]);
            }
        for (int i = 0;i < 4; i++) v.push_back(a[0][i]);
        for (int i = 3;i >= 0; i--) v.push_back(a[1][i]);
        for (int i = 0;i < 4; i++) v.push_back(a[2][i]);
        for (int i = 3;i >= 0; i--) v.push_back(a[3][i]);

        int sum = 0;
        for (int i = 0; i < 16; i++){
            if (v[i] == 0) continue;
            for (int j = i + 1; j < 16; j++){

                //cout << v[i] << " " << v[j] << endl;
                if (v[j] == 0) continue;
                if (v[i] > v[j]) sum++;
            }
        }
        if (sum & 1) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

H - K-th Closest Distance

 题意:给出一个序列,问序列的一个区间的第k靠近p的数是是什么(强制在线)

题解:二分+主席树

主席树可以用来求小于于k大的数的个数,我们二分[p-x, p+x]中的x,就可以知道[p -x, p+x]中的数的个数,如果个数大于k则缩小x,否则扩大x

#include <bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 2;
const int maxt = 1e6 + 2;
struct node{
    int l, r, sum, mx;
}M1[maxt * 50];

int a[maxn], root[maxn], cnt, n, m , bound;

void update(int l, int r, int &x, int y, int pos){
    M1[++cnt] = M1[y];
    M1[cnt].sum++;
    x = cnt;
    if (l == r) return;
    int mid = (l + r) / 2;
    if (pos <= mid) update(l, mid, M1[x].l, M1[y].l, pos);
    else update(mid + 1, r, M1[x].r, M1[y].r, pos);
}

int query1(int l, int r, int x, int y, int k){//大于等于k的数量
   if (k <= l) return M1[y].sum - M1[x].sum;
   int mid = (l + r) / 2;
   if (k <= mid) return M1[M1[y].r].sum - M1[M1[x].r].sum  + query1(l, mid, M1[x].l, M1[y].l, k);
   else return query1(mid+1, r, M1[x].r, M1[y].r, k);
}

int query3(int l, int r, int x, int y, int k){ //小于等于k的数量
   if (k >= r) return M1[y].sum - M1[x].sum;
   int mid = (l + r) / 2;
   if (k <= mid) return query3(l, mid, M1[x].l, M1[y].l, k);
   else return M1[M1[y].l].sum - M1[M1[x].l].sum + query3(mid+1, r, M1[x].r, M1[y].r, k);
}

int judge(int p, int x, int l, int r){
    int tmp = query1(1, bound, root[l - 1], root[r], p - x);
    tmp = r - l + 1 - tmp; // 小于p-x的数量
    int tmp2 = query3(1, bound, root[l - 1], root[r], p + x);//小于等于p+x的数量
    return tmp2 - tmp;
}

int main()
{
    int T;
    cin >> T;
    int X = 0;
    while (T--){
        bound = 0;
        scanf("%d%d", &n, &m);
        for (int i = 0; i <= n+1; i++) a[i] = root[i] = 0;
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]), bound = max(bound, a[i]);
        for (int i = 1; i <= n; i++) update(1, bound, root[i], root[i-1], a[i]);
        X = 0;
        for (int i = 1;i <= m;i++){
            int x, y, p, k;
            scanf("%d%d%d%d", &x, &y, &p, &k);
            x ^= X;y ^= X; p ^= X;k ^= X;
            int ll =0, rr = bound, ans = 0;
            while (ll <= rr){
                int mid = (ll + rr) / 2;
                if (judge(p, mid, x, y) >= k){
                    ans = mid;
                    rr = mid - 1;
                }else ll = mid + 1;
            }
            X = ans ;
            printf("%d\n", X);
        }
    }
    return 0;
}

J - Minimal Power of Prime

题意:给出一个n,如果素因数分解的幂次的最小值

题解:公式化 n=p_1^{k_1}\cdot p_2^{k_2} ... p_m^{k_m},求min\left \{ k|k_1, k_2, ..., k_m\right \}

素因子分解\sqrt[4]{n}内的所有素数,得到一个ans。如果还有素数大于\sqrt[4]{n},那么他的指数最大是4,如果他能开4次根号ans=min{ans, 4},如果能开3次根号,ans=min{ans, 3}, 如果能开根号,ans= min{ans, 2},如果不能开根号ans=min{ans, 1}

#include <bits/stdc++.h>
#define FOR(I,S,T) for(int I=(S);I<=(T);I++)
#define pb push_back
#define mp make_pair

using namespace std;
typedef long long ll;
const int maxn = 4000;
const int inf = 0x3f3f3f3f;
vector <int> p;
int vis[maxn + 10];
int cnt = 0;
void pri(){
    for (int i = 2; i <= maxn; i++){
        if (!vis[i]) p.pb(i);
        for (int j = 0; j < p.size() && i * p[j] <= maxn; j++){
            vis[i * p[j]] = 1;
            if (i % p[j] == 0) break;
        }
    }
}

int main()
{
    pri();
    int T;
    scanf("%d", &T);
    while (T--){
        ll n;
        scanf("%lld", &n);
        ll k = n, cnt = 0, ans = inf;
        for (int i = 0; i < p.size(); i++){
            if (n % p[i] == 0) cnt= 0;
            else continue;
            while (n % p[i] == 0) {
                cnt++;
                n /= p[i];
            }
            ans = min(cnt, ans);
        }

        ll tmp =  (ll)pow(n, 1.0/4);
        if (n == 1){
            printf("%lld\n", ans);
            continue;
        }
        if (tmp*tmp*tmp*tmp== n || (tmp-1)*(tmp-1)*(tmp-1)*(tmp-1) == n || (tmp+1)*(tmp+1)*(tmp+1)*(tmp+1) == n){
            ans= min(ans, 4ll);
            printf("%lld\n", ans);
            continue;
        }
        tmp = (ll)pow(n,1.0/3);
        if  (tmp*tmp*tmp == n || (tmp-1)*(tmp-1)*(tmp-1) == n || (tmp+1)*(tmp+1)*(tmp+1) == n) {
            ans= min(ans, 3ll);
            printf("%lld\n", ans);
            continue;
        }
        tmp = (ll)pow(n, 1.0/2);
        if (tmp*tmp == n || (tmp-1)*(tmp-1) == n || (tmp+1)*(tmp+1) == n) {
            ans= min(ans, 2ll);
            printf("%lld\n", ans);
            continue;
        }
        ans= min(ans, 1ll);
        printf("%lld\n", ans);
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值