2021HDU多校第一场题解

1001. Mod, Or and Everything

题意:

给你一个n(n \leq 10^{12}),求对于任意i \leq n,n \mod i按位或的和。

显然对于i \geq \frac{n}{2},n \mod i可以取到[1,\frac{n}{2}]的所有值,对于i \leq \frac{n}{2},n \mod i一定小于\frac{n}{2},所以答案一定为2的次幂 - 1,打表找一下具体是多少次幂即可

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <string>
#define debug(x) cout << #x << ":" << (x) << "\n";
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
const int maxm = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f;
ll n;

int main()
{    
    int _;
    // int a = 0;
    scanf("%d", &_);
    while ( _-- ) {
        scanf("%lld", &n);
        ll temp = 1;
        while ( temp * 2 < n ) {
            temp *= 2;
        }
        printf("%lld\n", temp - 1);
    }
    return 0;
}

1003. Puzzle loop

题意:

在方格中选择若干条边,使得它们形成若干个没有公共边的环,并且满足方格中数字的限制。

由于是若干个没有公共边的环,那么对于方格中的任意一点,都有与它相连的边中被选中的边数为偶数。我们将边被选中记为1,未选中记为0,则对于每一个点和每一个方格都可以列出一个异或方程,用高斯消元解这个异或方程组,若自由元个数为k,则答案为2^k。复杂度O((nm)^3)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <string>
#include <bitset>
#define debug(x) cout << #x << ":" << (x) << "\n";
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 10;
const int maxm = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f;
const int p = 998244353;
bitset<maxn> a[maxn];
int n, m;
int b[40][40];
int id[40][40];
int dx[] = { 1,0,-1,0 };
int dy[] = { 0,1,0,-1 };
int cnt = 0;
int ans = 0;
char ch[maxn];

void guass()
{
    int now = 1;
    for ( int i = 1;i <= id[0][0];i++ ) {
        for ( int j = now;j <= cnt;j++ ) {
            if ( a[j][i] ) {
                swap(a[j], a[now]);            
                break;
            }
        }
        if ( !a[now][i] )
            continue;
        for ( int j = 1;j <= cnt;j++ ) {
            if ( j == now )
                continue;
            if ( a[j][i] )
                a[j] ^= a[now];
        }
        now++;
    }
    for ( int i = now;i <= cnt;i++ ) {
        if ( a[i][id[0][0] + 1] ) {
            ans = -1;
            return;
        }
    }
    ans = id[0][0] - now + 1;
}

long long ksm(long long x, long long y)
{
    long long ret = 1;
    do {
        if ( y & 1 )
            ret = ret * x % p;
        x = x * x % p;
    } while ( y >>= 1 );
    return ret;
}

int main()
{
    // freopen("D:\\Codefield\\CPP\\HDU\\MINIEYE1\\in.txt", "r", stdin);
    // freopen("D:\\Codefield\\CPP\\HDU\\MINIEYE1\\out.txt", "w", stdout);
    int _;
    scanf("%d", &_);
    while ( _-- ) {
        scanf("%d%d", &n, &m);
        memset(b, 0, sizeof b);
        memset(id, 0, sizeof id);
        id[0][0] = 0;
        ans = 0;
        cnt = 0;
        int tt1 = 2 * n - 1;
        int tt2 = 2 * m - 1;
        for ( int i = 1;i <= n - 1;i++ ) {
            scanf("%s", ch + 1);
            for ( int j = 1;j <= m - 1;j++ ) {
                if ( ch[j] == '.' )
                    b[2 * i][2 * j] = -1;
                else b[2 * i][2 * j] = ch[j] - '0';
                // scanf("%d", &b[2 * i][2 * j]);
            }
        }
        for ( int i = 1;i <= tt1;i++ ) {
            for ( int j = 1;j <= tt2;j++ ) {
                if ( i % 2 == 1 && j % 2 == 1 )
                    continue;
                if ( i % 2 == 0 && j % 2 == 0 )
                    continue;
                id[i][j] = ++id[0][0];
            }
        }
        for ( int i = 1;i <= tt1;i++ ) {
            for ( int j = 1;j <= tt2;j++ ) {
                if ( i % 2 == 1 && j % 2 == 0 )
                    continue;
                if ( i % 2 == 0 && j % 2 == 1 )
                    continue;
                if ( b[i][j] == -1 )
                    continue;
                cnt++;
                a[cnt][id[0][0] + 1] = b[i][j];
                for ( int k = 0;k <= 3;k++ ) {
                    int x = i + dx[k];
                    int y = j + dy[k];
                    if ( x >= 1 && x <= tt1 && y >= 1 && y <= tt2 ) {
                        a[cnt][id[x][y]] = 1;
                    }
                }
            }
        }
        // for ( int i = 1;i <= cnt;i++ ) {
        //     for ( int j = 1;j <= id[0][0] + 1;j++ ) {
        //         // printf("%d ", a[i][j]);
        //         cout << a[i][j] << " ";
        //     }
        //     printf("\n");
        // }
        // printf("\n\n\n");
        guass();
        if ( ans == -1 ) {
            printf("0\n");
        }
        else
            printf("%lld\n", ksm(2ll, (long long)(ans)));
        for ( int i = 1;i <= cnt;i++ )
            a[i].reset();
    }
    return 0;
}

1005. Minimum spanning tree

题意:

给定一张无向完全图,点ij之间的边权为lcm(i,j),求最小生成树

显然对于质数p,我们选择它与2连的那条边,权值为2p,对于合数x,我们选择它与它的一个约数的相连的边,权值为xans = \sum_{i=1}^{n}i + \sum_p i,用欧拉筛预处理质数即可,复杂度O(n)

#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back

using namespace std;
const int maxn = 10000010;

int vis[maxn], prime[maxn];

void eulersieve(int x)
{
    int cnt = 0;
    for (int i = 2;i <= x;i++) {
        if (!vis[i]) {
            vis[i] = i;
            prime[++cnt] = i;
        }
        for (int j = 1;j <= cnt;j++) {
            if (i * prime[j] > x) {
                break;
            }
            vis[i * prime[j]] = prime[j];
            if (i % prime[j] == 0)
                break;
        }
    }
}

long long ans[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    eulersieve(10000000);
    int t, n, i;
    ans[2] = 0;
    for(i = 3; i <= 10000000; i++)
    {
        long long tmp = i;
        if(vis[i] == i) tmp *= 2;
        ans[i] = ans[i - 1] + tmp;
    } 
    cin >> t;
    while(t--){
        cin >> n;
        cout << ans[n] << endl;
    }
    return 0;
}

1006. Xor sum

题意:

给定一个序列a,和一个询问k,求满足异或和不小于k的最短连续子段。

首先预处理出前缀异或和,逐个

插入进01trie中,每次在01trie上进行查询,并更新答案。复杂度O(nlogn)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
const int maxn = 1e5 + 10;
int n, k;
int a[maxn];
int trie[maxn * 30][2];
int exist[maxn * 30];
int cnt;
int s[40];
int ansl, ansr;

int read()
{
    int x = 0, f = 1;
    char ch;
    while ( (ch = getchar()) < '0' || ch > '9' )
        if ( ch == '-' )
            f = -1;
    while ( ch >= '0' && ch <= '9' ) {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

void insert(int* s, int num)
{
    int p = 0;
    for ( int i = 30;i >= 1;i-- ) {
        if ( !trie[p][s[i]] ) {
            trie[p][s[i]] = ++cnt;        
            p = trie[p][s[i]];
        }
        else
            p = trie[p][s[i]];
        exist[p] = num;
    }
}

int find(int* s)
{
    int p = 0;
    int ret = -1;
    for ( int i = 30;i >= 1;i-- ) {
        if ( ((k >> (i - 1)) & 1) == 0 ) {
            if ( trie[p][s[i] ^ 1] )
                ret = max(ret, exist[trie[p][s[i] ^ 1]]);
            p = trie[p][s[i]];
        }
        else p = trie[p][s[i] ^ 1];
        if ( !p )
            return ret;
    }
    return max(ret, exist[p]);
}


void clear(int p = 0)
{
    if ( trie[p][0] )
        clear(trie[p][0]);
    if ( trie[p][1] )
        clear(trie[p][1]);
    trie[p][0] = trie[p][1] = 0;
    // exist[p] = 0;
}

int main()
{
    // freopen("D:\\Codefield\\CPP\\HDU\\MINIEYE1\\in.txt", "r", stdin);
    // freopen("D:\\Codefield\\CPP\\HDU\\MINIEYE1\\out2.txt", "w", stdout);
    int _ = read();
    // scanf("%d", &_);
    while ( _-- ) {
        cnt = 0;
        // scanf("%d%d", &n, &k);
        n = read();
        k = read();
        clear();
        a[0] = 0;
        for ( int i = 1;i <= n;i++ ) {
            // scanf("%d", &a[i]);
            a[i] = read();
            a[i] = a[i] ^ a[i - 1];
        }
        ansl = 0, ansr = n + 1;
        memset(s, 0, sizeof s);
        insert(s, 0);
        for ( int i = 1;i <= n;i++ ) {
            memset(s, 0, sizeof s);
            int temp = a[i];
            int num = 0;
            while ( temp ) {
                s[++num] = temp & 1;
                temp >>= 1;
            }
            int x = find(s);
            if ( x != -1 && ansr - ansl + 1 > i - x ) {
                ansr = i;
                ansl = x + 1;
            }
            insert(s, i);
        }
        if ( ansl ) {
            printf("%d %d\n", ansl, ansr);
        }
        else printf("-1\n");
    }
    return 0;
}

1007. Pass!

题意:

n个人进行传球,每个人可以传给其他任意一个人,求最少进行多少轮后可以球传回到1号的方案数为x \pmod {998244353}

首先很容易写出递推式,f[t] = (n - 2)f[t - 1] + (n - 1)f[t - 2]

由于n为定值,便可以转化为广义斐波那契数列求通项,解得f[t] = \frac{(n - 1)^t + (n - 1)(-1)^t}{n}

(n-1)^t \equiv nx - (n - 1)(-1)^t \pmod {998244353}

转化为离散对数的求解,分别讨论t的奇偶性,跑两次BSGS求解即可,复杂度O(\sqrt p)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
const double pi=acos(-1.0);
const int M=1e5+100;
const int mod=998244353;
inline int read(){
    int x=0,k=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-') k=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*k;
}
inline int GCD(int a,int b){return b?GCD(b,a%b):a;}
inline void write(int x){
    if(x<0) x=-x,putchar('-');
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int x,y,n,m,inv,T;
inline int ksm(int a,int b,int mo)
{
    int res=1;
    a%=mo;
    while(b)
    {
        if(b&1) res=(res*a)%mo;
        a=a*a%mo;
        b>>=1;
    }
    return res%mo;
}
inline int BSGS(int a,int b,int p)
{
    if(b==1) return 0;
    int m=ceil(sqrt(p));
    int t=b;
    unordered_map<int,int>dis;
    dis[b]=0;
    for(int j=1;j<m;j++)
    {
        t=t*a%p;
        dis[t]=j;
    }
    int mi=ksm(a,m,p);
    t=1;
    for(int i=1;i<=m;i++)
    {
        t=t*mi%p;
        if(dis.count(t)!=0) return (i*m-dis[t]);
    }
    return -1;
}
signed main()
{
    cin>>T;
    while(T--)
    {
        cin>>n>>x;
        int A=n-1;
        int x1=(x*n+n-1+mod)%mod;
        int t1=BSGS(A,x1,mod); //奇数
    //    printf("t1的值为:%lld\n",t1);
        if(t1%2==0&&t1!=-1) t1=-1;
        int x2=(x*n-n+1+mod)%mod;
        int t2=BSGS(A,x2,mod); //偶数
    //    printf("t2的值为:%lld\n",t2);
        if(t2%2==1&&t2!=-1) t2=-1;
        if(t1==-1&&t2==-1)
        {
            printf("-1\n");
            continue;
        }
        if(t1==-1&&t2!=-1)
        {
            printf("%lld\n",t2);
            continue;
        }
        if(t1!=-1&&t2==-1)
        {
            printf("%lld\n",t1);
            continue;
        }
        printf("%lld\n",min(t1,t2));
    }
    return 0;
}

1008. Maximal submatrix

题意:

给定一个矩阵,找到最大子矩阵,满足每一列的数字单调不减。

建一个新的矩阵,处理出所有a[i][j] < a[i - 1][j]的位置,跑一个最大子矩阵即可。

#include <bits/stdc++.h>
#define x first
#define y second
#define pb push_back

using namespace std;
const int maxn = 2010;

int a[maxn][maxn], pre[maxn][maxn], l[maxn][maxn], r[maxn][maxn], q[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, m, t, i, j, top;
    cin >> t;
    //srand(time(NULL));
    while (t--) {
        cin >> n >> m;
        for (i = 1; i <= n; i++)
            for (j = 1; j <= m; j++) cin >> a[i][j];
        for (i = 1; i <= m; i++) {
            pre[1][i] = 1;
            for (j = 2; j <= n; j++) {
                if (a[j][i] < a[j - 1][i]) pre[j][i] = j;
                else pre[j][i] = pre[j - 1][i];
            }
        }
        for (i = 1; i <= n; i++)
        {
            top = 0;
            for (j = 1; j <= m; j++) {
                while (top && pre[i][j] > pre[i][q[top]]) {
                    r[i][q[top]] = j - 1;
                    --top;
                }
                q[++top] = j;
            }
            while (top) {
                r[i][q[top]] = m;
                --top;
            }
        }
        for (i = 1; i <= n; i++) {
            top = 0;
            for (j = m; j; j--) {
                while (top && pre[i][j] > pre[i][q[top]]) {
                    l[i][q[top]] = j + 1;
                    --top;
                }
                q[++top] = j;
            }
            while (top) {
                l[i][q[top]] = 1;
                --top;
            }
        }
        int ans = m;
        for (i = 1; i <= n; i++) {
            for (j = 1; j <= m; j++) {
                ans = max(ans, (r[i][j] - l[i][j] + 1) * (i - pre[i][j] + 1));
            }
        }
        cout << ans << endl;
    }
    return 0;
}

1009. KD-Graph

题意:

给定一张图,求一个最小生成森林,使得森林中正好有K个连通块。如果加入了权值为w的边,则所有权值\leq w的边都必须被加入。

将边权从小到大排序,二分加入的最大边,判断是否可行即可。

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <string>
#define debug(x) cout << #x << ":" << (x) << "\n";
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int maxm = 5e5 + 10;
const int inf = 0x3f3f3f3f;
const ll llinf = 0x3f3f3f3f3f3f3f;
struct Edge {
    int val, to, next;
}e[maxm << 1];
struct EDGE {
    int u, v, w;
    bool operator<(const EDGE& x)const
    {
        return w < x.w;
    }
}E[maxn];
int fa[maxn];
int head[maxn];
int tot;
int num;
int n, m, k;

int find(int x)
{
    if ( fa[x] != x )
        fa[x] = find(fa[x]);
    return fa[x];
}

void merge(int x, int y)
{
    x = find(x);
    y = find(y);
    if ( x == y )
        return;
    fa[x] = fa[y];
    num--;
}

void addedge(int u, int v, int w)
{
    e[++tot] = Edge({ w,v,head[u] });
    head[u] = tot;
}

int check(int x)
{
    for ( int i = 1;i <= n;i++ )
        fa[i] = i;
    num = n;
    for ( int i = 1;i <= m;i++ ) {
        if ( E[i].w > x) {
            break;
        }
        merge(E[i].u, E[i].v);
    }
    if ( num > k ) {
        return 1;
    }
    else if ( num == k )
        return 0;
    else return -1;
}

int main()
{    
    int _;
    int a = 0;
    scanf("%d", &_);
    int u, v, w;
    while ( _-- ) {
        scanf("%d%d%d", &n, &m, &k);
        int l = 0, r = 0;
        for ( int i = 1;i <= m;i++ ) {
            scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].w);
            r = max(r, E[i].w);
            // addedge(u, v, w);
            // addedge(v, u, w);
        }
        sort(E + 1, E + m + 1);
        int ans = -1;
        while ( l < r ) {
            int mid = (l + r) >> 1;
            if ( check(mid) == 1 ) {
                l = mid + 1;
            }
            else if( check(mid) == 0 ){
                ans = mid;
                r = mid;
            }
            else {
                r = mid;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

1010. zoto

题意:

给定一系列点(i,f[i]),m次询问,每次询问横坐标在[x_0,x_1]中有多少个在[y_0,y_1]的纵坐标。

显然需要用到莫队,但普通的莫队无法查询在某个区间内不同权值的数量,单次暴力查询的复杂度为O(n)无法接受。我们发现使用权值线段树可以实现单次O(logn)的查询复杂度,但莫队每次修改的复杂度却由O(1)变为O(logn),总的修改复杂度变为O(n\sqrt n logn),也难以接受。我们需要找到一个O(1)修改并且查询复杂度可接受的数据结构。容易想到对值域进行分块,便可以做到O(1)

修改,单次O(\sqrt n) 查询。总的复杂度为O(n\sqrt n)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 1e5 + 10;
int st[maxn], ed[maxn], bel[maxn];
int n, m;
struct QQ {
    int x0, x1, y0, y1;
    int id;
    bool operator<(const QQ& x)const
    {
        if ( bel[x0] != bel[x.x0] ) {
            return x0 < x.x0;
        }
        return bel[x1] < bel[x.x1];
    }
}q[maxn];
int num[maxn];
int cnt[maxn];
int f[maxn];
int ans[maxn];

void pre()
{
    int sqr = sqrt(maxn - 10);
    for ( int i = 1;i <= sqr;i++ ) {
        st[i] = (i - 1) * sqr + 1;
        ed[i] = i * sqr;
    }
    st[1] = 0;
    ed[sqr] = n;
    for ( int i = 1;i <= sqr;i++ ) {
        for ( int j = st[i];j <= ed[i];j++ ) {
            bel[j] = i;
        }
    }
}

void add(int x)
{
    if ( !num[f[x]] ) {
        cnt[bel[f[x]]]++;
    }
    num[f[x]]++;
}

void del(int x)
{
    if ( num[f[x]] == 1 ) {
        cnt[bel[f[x]]]--;
    }
    num[f[x]]--;
}

int query(int l, int r)
{
    int ret = 0;
    if ( bel[l] == bel[r] ) {
        for ( int i = l;i <= r;i++ )
            if ( num[i] )
                ret++;
        return ret;
    }
    for ( int i = l;i <= ed[bel[l]];i++ ) {
        if ( num[i] )
            ret++;
    }
    for ( int i = st[bel[r]];i <= r;i++ )
        if ( num[i] )
            ret++;
    for ( int i = bel[l] + 1;i <= bel[r] - 1;i++ ) {
        ret += cnt[i];
    }
    return ret;
}

int main()
{
    int _;
    scanf("%d", &_);
    pre();
    while ( _-- ) {
        scanf("%d%d", &n, &m);
        memset(num, 0, sizeof num);
        memset(cnt, 0, sizeof cnt);
        for ( int i = 1;i <= n;i++ ) {
            scanf("%d", &f[i]);
        }
        for ( int i = 1;i <= m;i++ ) {
            scanf("%d%d%d%d", &q[i].x0, &q[i].y0, &q[i].x1, &q[i].y1);
            q[i].id = i;
        }
        sort(q + 1, q + m + 1);
        int l = 1, r = 1;
        add(l);
        for ( int i = 1;i <= m;i++ ) {
            int L = q[i].x0, R = q[i].x1;
            while ( l > L ) {
                add(--l);
            }
            while ( r < R ) {
                add(++r);
            }
            while ( l < L ) {
                del(l++);
            }
            while ( r > R ) {
                del(r--);
            }
            ans[q[i].id] = query(q[i].y0, q[i].y1);
        }
        for ( int i = 1;i <= m;i++ ) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值