2021“MINIEYE杯”中國大學生算法設計超級聯賽 第一場題解


ACM暑期多校聯合賽 題解

6950 Mod, Or and Everything

 簽到題

  題意:給定一個整數N,求(n mod 1) or (n mod 2) or ... or (n mod (n - 1)) or (n mod n).

  思路:
  “肯定是先打表找規律啊!”

  於是,我們會發現:
  當N和答案的關系是,求K = log(n - 1)/log(2) + 1(一個K,使得N 恰好 小於 (1<<K))

  答案即為(1ll<<(K - 1) - 1)

  注意,我們需要特判一下n = 1的情況(顯然!log(0)是不存在的!)

  代碼:

點擊查看代碼
#include 
#include  
using namespace std;
typedef long long ll;
int main(){
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ll n;
        cin>>n;
        if(n == 1)
        {
            printf("0\n");
            continue;
        }
        n = log(n - 1)/log(2) + 1;
        cout<<((1ll<<(n - 1)) - 1)<<endl;
    }
    return 0;
}

6954 Minimum spanning tree

簽到題

    題意:給定一個數N,求2~N中所有點構成一顆最小生成樹,使得其每條邊的權值和最小(廢話),每條邊的權值定義為lcm(a,b)(a,b的最小公倍數),a,b分別為一條邊的兩個節點編號

   思路:仔細分析我們可以得到一下結論

   如果一個數是質數,那么與它連的邊的權值至少是 這個質數*2

   如果一個數不是質數,那么與它連的邊的權值至少是這個數

   比如一個數15,我們既然能夠取到15這個數,那么也就必然存在3 5這兩個結點,那么我們只需要將15連上3 or 5就能保證連了15這個節點的邊最小了

   蕪湖,這樣我們就貪心得到了一顆最小生成樹啦!

   方法:

   O(1e7)歐拉篩篩質數

   O(1e7)打表存ans數組

    O(q)輸出詢問 

   代碼:

    

點擊查看代碼
#include <iostream>
#include <cmath> 
using namespace std;
typedef long long ll;
const int MAXN = 1e7 + 7;
int prime[MAXN];
int p[MAXN],top = 0;
ll ans[MAXN];
void ini()
{
    for(int i = 2;i < MAXN;i++)
    {
        if(!prime[i])    p[++top] = i;
        for(int j = 1;j <= top && p[j]*i < MAXN;j++)
        {
            prime[p[j]*i] = 1;
            if(i%p[j] == 0)    break; 
        }
    }
    
    ans[2] = 0;
    for(int i=3;i < MAXN;i++){
        if(!prime[i])    ans[i] = ans[i - 1] + i*2;
        else    ans[i] = ans[i - 1] + i;
    }
}
int main(){
    int t;
    scanf("%d",&t);
    ini();
    while(t--)
    {
        ll n;
        cin>>n;
        cout<<ans[n]<<endl;
    }
    return 0;
} 

6955 Xor sum

字典樹

    題意:給定一段長為N的序列,求其中一段最小的子序列,使得這段子序列的異或和大於給定的K。輸出子序列的左右端點。

    思路:

    首先這道題肯定不能O(n^2)

    問題轉化:由於xor具有自反性,那么對於一段異或前綴和s[j]和s[i],s[i]^s[j] = a[i +1]^a[i + 2]^... ^a[j];

    so我們就能將問題轉化為求一串序列中,尋找間隔最小的兩個數,使得兩個數的異或值大於給定的K。輸出這兩個數的下標。

    考慮字典樹,枚舉每一個右端點,這個端點之前的所有數已加入到字典樹中。

    我們貪心地去尋找與當前右端點的數A異或最大的那個在它左邊的數,返回下標,如果這個最大值大於K的話更新答案。

    這里說下為什么每次貪心找異或最大的那個數(其實只要大於K都行)就能保證是最優的:

    似乎是構造這樣一個測試點很困難(我也沒構造...本蒟蒻也不會證QAQ,隊友過了我都是蒙的...)

    代碼:

點擊查看代碼
#include <iostream>
#include <cstring>
#include <cmath> 
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 7;
const int inf = 1e9 + 7;
int t[MAXN*31][3],tot = 0;
int a[MAXN];
void ins(int x,int idx)
{
    int p = 0;
    for(int i = 30;i >= 0;i--)
    {
        int u = x >> i & 1;
        if(!t[p][u])    t[p][u] = ++tot;
        p = t[p][u];
    }
    t[p][2] = idx;
}
int Go(int x)
{
    int p = 0;
    for(int i = 30;i >= 0;i--)
    {
        int u = x >> i & 1;
        if(t[p][!u])    p = t[p][!u];
        else    p = t[p][u];
        if(!p)
        return -1;//說明不存在 
    }
    return t[p][2];
}
int main(){
    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        for(int i = 0;i <= tot;i++)
        t[i][0] = t[i][1] = t[i][2] = 0;
        tot = 0;
        int n,k;
        scanf("%d %d",&n,&k);
        int ls = 0;
        int le,ri;
        int ans = inf;
        for(int i = 1;i <= n;i++)
        {
            int x;
            scanf("%d",&x);
            ls ^= x;
            a[i] = ls;
            int now = Go(ls);
            
            if((a[now]^ls) >= k)
            {
                if(i - now < ans)
                {
                    ans = i - now;
                    le = now + 1;
                    ri = i;
                }
            }
            ins(ls,i);
        }
        if(ans == inf)
        puts("-1");
        else
        printf("%d %d\n",le,ri);
    }
    return 0;
} 

     這里有個小技巧,就是每次清空字典樹。如果我們直接調用memset對所有的節點清空的話,每次都會花費31*1e5,有些耗時。

     其實我們每次只要清空之前使用過的那個節點就行。

6957 Maximal submatrix

單調棧

     題意:給定一個矩陣,求一個最大的子矩陣(最大面積),使得子矩陣中每列元素都是遞增的......

     思路:

     如果我們把輸入的那個矩陣按照 列的遞增個數前綴和 來表示的話,會得到差不多這樣的一個矩陣:

    原矩陣:

    1 2 4

    2 3 3

     列遞增前綴和矩陣(我自己取得名字方便描述)

    1 1 1

    2 2 1

    我們會發現對於每一行會記錄下之前行遞增的個數,這有利於我們求面積!

    我們考慮一下再這一行的數字的遞增性質,會發現和單調棧的模板題非常像!(其實就是...)

    我們直接類比過來即可...

    於是問題轉化為枚舉每一行,求這一行能夠構成的最大子矩陣面積,然后再更新最終的答案。

    代碼:

    

點擊查看代碼
#include <iostream>
using namespace std;
const int MAXN = 2e3 + 7;
int n,m;
int a[MAXN][MAXN];
int sum[MAXN][MAXN];
int stk[MAXN],top = 0;
int w[MAXN];
int now[MAXN];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&n,&m);
        for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
        scanf("%d",&a[i][j]);
        
        for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
        {
            if(a[i][j] >= a[i - 1][j])
            sum[i][j] = sum[i - 1][j] + 1;
            else
            sum[i][j] = 1;
        }
        
        int res =  0;
        for(int i = 1;i <= n;i++)
        {
            top = 0;
            for(int j = 1;j <= m;j++)
            now[j] = sum[i][j];
            
            int ans = 0;
            for(int j = 1;j <= m;j++)
            {
                int x = now[j];
                if(top == 0 || stk[top] <= x)
                stk[++top] = x,w[top] = 1;
                else
                {
                    int wid = 0;
                    while(top && stk[top] > x)
                    {
                        wid += w[top];
                        ans = max(ans,wid*stk[top]);
                        top -= 1;
                    }
                    stk[++top] = x;
                    w[top] = wid + 1;
                }
            }
            int wid = 0;
            while(top){
                wid += w[top];
                ans = max(ans,stk[top]*wid);
                top -= 1;
            }
            res = max(ans,res);
        }
        printf("%d\n",res);
    }
    return 0;
}

6958 KD-Graph

貪心+並查集

     題意:給定一個圖,求一個長度D,使得這個圖可以分成K份(割出點分成每一份),滿足每一份之中的兩兩之間的距離 <= D,而份之間距離要 > D(當然也可以兩個點不可達)

    思路:

    由於需要將所有的點進行分份,然后滿足一定條件的邊的權值太會加入到圖中,於是我們可以從大到小枚舉每一條邊,然后每次將當前權值相同的所有邊加入圖中,如果某一次加完當前階段的所有邊(權值相同的邊作為一個合並階段),圖中的點恰好分成了K份,那么這個邊的權值就是答案!

    感性證明:

    在這個D之前的所有邊都會加入到圖中,使得我們的圖中恰好存在K塊,使得塊與塊之間不可達,而塊內相互可達!這是毫無疑問的。

    而后我們加入剩下的所有邊,這些邊不會影響塊與塊之間的可達性(因為大於D,使得我們可以不承認塊連接起來了!)

    證畢。

    代碼:

     

點擊查看代碼
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 500005;
struct node
{
    int x,y,w;
}a[MAXN];
int n,m,k;
bool cmp(node a,node b)
{return a.w < b.w;}
int fa[MAXN/5];
void ini()
{
    int Max = MAXN/5;
    for(int i = 0;i < Max;i++)
    fa[i] = i;
}
int Find(int x)
{
    if(x == fa[x])    return x;
    return fa[x] = Find(fa[x]);
}
bool merge(int x,int y)
{
    x = Find(x);
    y = Find(y);
    if(x != y)
    {
        fa[x] = y;
        return true;
    }
    return false;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ini();
        scanf("%d %d %d",&n,&m,&k);
        for(int i = 0;i < m;i++)
        scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].w);
        if(n == k)
        {
            puts("0");
            continue;
        }
        
        sort(a,a + m,cmp);
        int now = n;
        int ans = -1;
        for(int i = 0;i < m;)
        {
            int cur_w = a[i].w;
            while(i < m && cur_w == a[i].w)
            {
                int x = a[i].x;
                int y = a[i].y;
                if(merge(x,y))
                now -= 1;
                
                i += 1;
            }
            if(now == k)
            {
                ans = cur_w;
                break;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

6959 zoto

莫隊

     賽后牢騷:

     !!!真彩!!!分明離線查詢很容易就想到莫隊的嘛!

     題意:給定一些二維左邊上的點,他們的x左邊從1~n,y軸坐標在[0,100000]范圍內。給定一個查詢矩陣(x0,y0) (x1,y1)求在這個矩陣當中有多少個不同的y值大小不同的點。

     思路:

     離線查詢->莫隊

     我們先求出第一次查詢的結果,然后根據第一次查詢結果可以得到相鄰區間查詢的結果,優雅的暴力~

     這里只說下怎么由一個區間到另外一個區間...這里是二維的點啊喂!

     我們開一個數組num存放當前x0 - x1區間內所有的y值出現的次數(一個桶,num[y]表示y坐標出現的次數)

     然后我們對num進行分塊 -> sum數組,每一塊中保存其中不同y的個數

     這樣對於每一次的區間x0 - x1的統計點,然后計算分布在y0 - y1就好啦!

     計算y0 - y1直接前綴和思想即可(什么,你不知道?就是計算0 ~ y1滿足條件的y的個數 - 0 ~ y0 - 1中滿足條件的y的個數)

     區間轉移:
     相信你很容易就明白下面這兩個函數:

void add(int x)
{
    num[fx[x]] += 1;
    if(num[fx[x]] == 1)    sum[fx[x]/K] += 1;
}
void del(int x)
{
    num[fx[x]] -= 1;
    if(num[fx[x]] == 0)    sum[fx[x]/K] -= 1; 
}

     add函數,將當前x軸上的點加入到num,sum數組中

     num就不說啦,看sum,由於我們只統計不重復的,所以統計第一次出現的那個y就好了,然后分塊,分塊大小K = sqrt(n)

     del函數也是一樣的道理的啦!

     然后是計算0~y有多少個不重復的y:

int cal(int y)
{
    int ans = 0;
    int R = y/K;
    for(int i = 0;i < R;i++)    ans += sum[i];
    for(int i = R*K;i <= y;i++)    ans += (num[i] >= 1);
    return ans;     
}

      塊內直接統計sum,離散部分暴力統計即可~

     完整代碼:

     

點擊查看代碼
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int MAXN = 100005;
int K;
struct node
{
    int l,r;
    int u,d;
    int idx;
    bool operator < (const node &a)const
    {
        if(l/K != a.l/K)    return l < a.l;
        if((l/K)&1)    return r > a.r;
        return r < a.r;    
    }
}a[MAXN];
int num[MAXN];//統計對應y下的x的個數 
int sum[MAXN];//分塊中的各個值 
int ans[MAXN];
int fx[MAXN];
void add(int x)
{
    num[fx[x]] += 1;
    if(num[fx[x]] == 1)    sum[fx[x]/K] += 1;
}
void del(int x)
{
    num[fx[x]] -= 1;
    if(num[fx[x]] == 0)    sum[fx[x]/K] -= 1; 
}
int cal(int y)
{
    int ans = 0;
    int R = y/K;
    for(int i = 0;i < R;i++)    ans += sum[i];
    for(int i = R*K;i <= y;i++)    ans += (num[i] >= 1);
    return ans;     
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(num,0,sizeof num);
        memset(sum,0,sizeof sum);
        int n,m;
        scanf("%d %d",&n,&m);
        for(int i = 1;i <= n;i++)    scanf("%d",&fx[i]);
        K = sqrt(n + 0.5);
        
        for(int i = 0;i < m;i++)
        scanf("%d %d %d %d",&a[i].l,&a[i].d,&a[i].r,&a[i].u),a[i].idx = i;
        
        sort(a,a + m);
        int le,ri;//先暴力算第一個 
        le = a[0].l,ri = a[0].r;
        for(int i = le;i <= ri;i++)    add(i);
        int now = cal(a[0].u) - cal(a[0].d - 1);
        ans[a[0].idx] = now;
        for(int i = 1;i < m;i++)
        {
            while(le < a[i].l)
            del(le++);
            while(le > a[i].l)
            add(--le);
             
            while(ri < a[i].r)
            add(++ri);
            while(ri > a[i].r)
            del(ri--);
            
            now = cal(a[i].u) - cal(a[i].d - 1);
            ans[a[i].idx] = now;
        }
        for(int i = 0;i < m;i++)
        printf("%d\n",ans[i]);
    }
    return 0;
}

未完待續....(其實是太菜了,其他的題還不會)

知識點預告:高斯消元、KD-Tree、數學公式QAQs

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM