【解题报告】ZJNU 个人赛二(2021)

B

  • 暴力跑

C

题意

  • N N N 个点, K K K种颜色。第 i i i 个点颜色为 b i b_i bi
    给定一个矩阵 S S S,其中 S i , j = 1 S_{i,j}=1 Si,j=1 表示 i i i 颜色能向 j j j 颜色连边。
    其中 S i , j S_{i,j} Si,j 不一定等于 S j , i S_{j,i} Sj,i S i , i S_{i,i} Si,i 也不一定等于 1 1 1
    若第 i i i 个点向第 j j j 个点连边,则边权为 ∣ i − j ∣ |i-j| ij
  • 问你第一个点走到最后一个点的最短距离。
    1 ≤ N ≤ 5 e 4 1\le N\le 5e4 1N5e4
    1 ≤ K ≤ 50 1\le K\le 50 1K50

思路

  • 首先,如果把所有的边都连上,时空复杂度都不够的。
  • 我们(wo)一开始会想到:对于 i i i 这个点,对于所有的颜色 j j j,我们连边只连最近的那些颜色为 j j j 的点(左边一个右边一个,如果有的话)。
    比如 b [ ] = { 2 , 3 , 3 , 3 } b[]=\{2,3,3,3\} b[]={2,3,3,3},且 S 2 , 3 = 1 S_{2,3}=1 S2,3=1,我们连边只连 < 1 → 2 > <1\rightarrow 2> <12>,省略了边 < 1 → 3 > <1\rightarrow 3> <13> < 1 → 4 > <1\rightarrow 4> <14>
    然后去跑一个最短路。
    但是这样会 W A WA WA
  • 我们考虑为什么会 W A WA WA,原因就是因为我们省略了一(hao)些(duo)边,会导致一些问题:
    对于上面那个例子, b [ ] = { 2 , 3 , 3 , 3 } b[]=\{2,3,3,3\} b[]={2,3,3,3} S 2 , 3 = 1 S_{2,3}=1 S2,3=1 但是 S 3 , 3 = 0 S_{3,3}=0 S3,3=0
    然后我们就无法从第一个点跑到第三个点和第四个点去了。
  • 我们想到,如果 颜色 i i i 向颜色 j j j 连边了,且 p o s i < p o s j pos_i<pos_j posi<posj,那么对于所有的 p ≥ p o s j p\ge pos_j pposj,且颜色仍为 j j j的点, 我们都能连边 < p o s i → p > <pos_i\rightarrow p> <posip>
    在上面的例子中:因为 < 1 → 2 > <1\rightarrow 2> <12>,且 b 2 = b 3 = b 4 b_2=b_3=b_4 b2=b3=b4,所以我们能连边 < 1 → 3 > <1\rightarrow 3> <13> < 1 → 4 > <1\rightarrow 4> <14>
  • 考虑到 p a < p b < p c p_a<p_b<p_c pa<pb<pc,a连向b边权为 ∣ p b − p a ∣ |p_b-p_a| pbpa,a连向c边权为 ∣ p c − p a ∣ |p_c-p_a| pcpa ,b连向c边权为 ∣ p c − p b ∣ |p_c-p_b| pcpb
    所以这两种建图方式等价:
    (1) < a → b > <a\rightarrow b> <ab> < a → c > <a\rightarrow c> <ac>
    (2) < a → b > <a\rightarrow b> <ab> < b → c > <b\rightarrow c> <bc>
  • 所以我们把 < p o s i → p > <pos_i\rightarrow p> <posip> 从第一种建图方式变成第二种建图方式,看图吧:
    在这里插入图片描述
  • 然后从右往左连边的方式也差不多等价。
    这样边数量级会变成 O ( n k ) O(nk) O(nk)

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 5e4+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;
int n,k;
int aa[MAX];
int pre[55],suf[55];
char can[55][55];
vector<pair<int,ll> >V[MAX];
ll dis[MAX];
void dijkstra(int s,int t){
    dis[s] = 0;
    priority_queue<pair<ll,int> >Q;
    Q.push(make_pair(0,s));
    while(!Q.empty()){
        int x = Q.top().second;
        ll d =-Q.top().first;
        Q.pop();
        if(d > dis[x])continue;
        for(auto it : V[x]){
            int y = it.first;
            ll w = it.second;
            if(dis[y] > dis[x] + w){
                dis[y] = dis[x] + w;
                Q.push(make_pair(-dis[y],y));
            }
        }
    }
}
int qian[55],hou[55];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;++i){
        scanf("%d",&aa[i]);
    }
    for(int i = 1;i <= k;++i){
        scanf("%s",can[i]+1);
    }

//    for(int i = 1;i <= k;++i){
//        for(int j = 1;j <= k;++j){
//            cout << can[i][j];
//        }
//        puts("");
//    }
    for(int i = 1;i <= n;++i)dis[i] = LINF;
    for(int i = 1;i <= k;++i){
        pre[i] = suf[i] = INF;
        hou[i] = n+1;
        qian[i] = 0;
    }
    pre[aa[1]] = 1;
    suf[aa[n]] = n;
    for(int i = 2;i <= n;++i){
        for(int j = 1;j <= k;++j){
            if(pre[j] == INF)continue;
            if(can[aa[i]][j] == '1'){
                V[i].push_back(make_pair(pre[j],i-pre[j]));
                qian[j] = max(qian[j],pre[j]);
            }
            if(can[j][aa[i]] == '1'){
                V[pre[j]].push_back(make_pair(i,i-pre[j]));
                hou[aa[i]] = min(hou[aa[i]],i);
            }
        }
        pre[aa[i]] = i;
    }
    for(int i = n-1;i >= 1;--i){
        for(int j = 1;j <= k;++j){
            if(suf[j] == INF)continue;
            if(can[aa[i]][j] == '1'){
                V[i].push_back(make_pair(suf[j],suf[j] - i));
                hou[j] = min(hou[j],suf[j]);
            }
            if(can[j][aa[i]] == '1'){
                V[suf[j]].push_back(make_pair(i,suf[j] - i));
                qian[aa[i]] = max(qian[aa[i]],i);
            }
        }
        suf[aa[i]] = i;
    }

//    for(int i = 1;i <= n;++i){
//        for(auto it : V[i]){
//            show(i,it.first);
//        }
//    }
//    puts("---");
    for(int i = 1;i <= k;++i){
//        show(qian[i],hou[i]);
        for(int j = hou[i]+1;j <= n;++j){
            if(aa[j] == i){
                V[hou[i]].push_back(make_pair(j,j-hou[i]));
            }
        }
        for(int j = qian[i]-1;j >= 1;--j){
            if(aa[j] == i){
                V[qian[i]].push_back(make_pair(j,qian[i]-j));
            }
        }
    }
//    puts("---");
//    for(int i = 1;i <= n;++i){
//        for(auto it : V[i]){
//            show(i,it.first);
//        }
//    }
    dijkstra(1,n);

//    for(int i = 1;i <= n;++i){
//        show(dis[i]);
//    }

    if(dis[n] == LINF)puts("-1");
    else printf("%lld",dis[n]);
    return 0;
}
/**
5 2
1 1 1 2 2
01
00
*/

D(新增)

题意

  • 给定一个字符串 S S S。你需要构造一个(但不用给出) 字母表的一个排列 T T T
    然后把 S S S 进行划分,使得每一个划分 S i S_i Si 都是 T T T 的子序列。
    求出最少的划分数。
  • ∣ S ∣ ≤ 1 0 5 |S|\le 10^5 S105
    其中出现的不同字母个数 C = ∣ ∑ ∣ ≤ 20 C=|\sum|\le 20 C=20

思路

  • 是个状压dp。因为 20 20 20,最诱惑的当然是 20 × 2 20 20\times2^{20} 20×220了。
    我们需要一种字母一种字母加进来。
    为什么呢?因为假设原来加进来了集合为 U U U的字母,再加进去新字母 i i i 的花费和之前加进来集合 U U U 内字母的先后顺序无关
  • f [ U ] f[U] f[U] 表示考虑好字母的集合为 U U U 的最小花费
    g [ i ] [ U ] g[i][U] g[i][U] 表示考虑好字母的集合为 U U U,新加进来字母 i i i 的额外花费
    c o s t [ i ] [ j ] cost[i][j] cost[i][j] 表示原来字符串 S S S 中,字母 i i i j j j 的相邻个数。
  • 我们最优划分是怎么样的呢?如果字母表排列为 a b ab ab,字符串中子串为 b a ba ba 的地方,这里必须划分开来,是必须的也是最优的。也就是说如果字母表就是 a b c d . . . abcd... abcd... 那么我们的花费为:
    ∑ i = 1 26 ∑ j = 1 i − 1 c o s t [ i ] [ j ] \sum_{i=1}^{26}\sum_{j=1}^{i-1}cost[i][j] i=126j=1i1cost[i][j]
  • 这里变成了数位 d p dp dp,我们记录的是已经加进去的字母集合,每次新加进去一个字母,然后考虑 c o s t cost cost 对答案的影响。
  • 首先这个 c o s t [ i ] [ j ] cost[i][j] cost[i][j] 很好获得,但是要注意字母是离散化之后的字母。
  • 然后是这个 f [ U ] f[U] f[U]。我们考虑新加进来字母 i i i
    那么这个字母必须是新的,也就是 ! ( U & ( 1 < < i ) ) !(U\&(1<<i)) !(U&(1<<i))
    加进来之后,集合 U U U 变成了 U ∣ ( 1 < < i ) U|(1<<i) U(1<<i)
    花费呢?就是 g [ i ] [ U ∣ ( 1 < < i ) ] g[i][U|(1<<i)] g[i][U(1<<i)]
    也就是:
    f [ U ∣ ( 1 < < i ) ] = min ⁡ { f [ U ] + g [ i ] [ U ∣ ( 1 < < i ) ] } f[U|(1<<i)]=\min\{ f[U]+g[i][U|(1<<i)] \} f[U(1<<i)]=min{f[U]+g[i][U(1<<i)]}
    注意为什么不是加上 g [ i ] [ U ] g[i][U] g[i][U] 呢?因为相邻相同字母也是不合法的。
  • 然后是这个 g [ i ] [ U ] g[i][U] g[i][U]
    g [ i ] [ U ] = g [ i ] [ U − ( 1 < < k ) ] + c o s t [ i ] [ j ]      其 中 ∀ k ∈ U g[i][U]=g[i][U-(1<<k)]+cost[i][j]\ \ \ \ 其中 \forall k\in U g[i][U]=g[i][U(1<<k)]+cost[i][j]    kU
    但是时间复杂度可能会变成 O ( 2 0 2 × 2 20 ) O(20^2\times2^{20}) O(202×220),因为我们是一个一个去找这个 k k k的。
    我们把这个 k k k 给优化一下变成:
    g [ i ] [ U ] = g [ i ] [ U − ( 1 < < k ) ] + c o s t [ i ] [ j ]      其 中 l o w b i t ( U ) = ( 1 < < k ) g[i][U]=g[i][U-(1<<k)]+cost[i][j]\ \ \ \ 其中lowbit(U)=(1<<k) g[i][U]=g[i][U(1<<k)]+cost[i][j]    lowbit(U)=(1<<k)
    时间复杂度变为 O ( C × 2 C ) O(C\times2^{C}) O(C×2C)

代码

  • 时间复杂度: O ( C × 2 C ) O(C\times2^{C}) O(C×2C)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

char ss[MAX];
set<char>S;
map<char,int>M;
int aa[MAX];
int cost[25][25];
int f[(1<<21)];
int g[25][(1<<21)];
unordered_map<int,int>GAO;
int main()
{
    int n;
    scanf("%s",ss+1);
    n = strlen(ss+1);
    for(int i = 1;i <= n;++i){
        S.insert(ss[i]);
    }
    int id = 0;
    for(auto it : S){
        M[it] = id++;
    }
    for(int i = 1;i <= n;++i){
        aa[i] = M[ss[i]];
        if(i > 1){
            cost[aa[i]][aa[i-1]]++;
        }
    }

    for(int i = 1;i < (1<<id);++i){
        f[i] = INF;
    }

    for(int i = 0;i < id;++i){
        GAO[(1<<i)] = i;
    }


    for(int i = 1;i < (1<<id);++i){

        for(int j = 0;j < id;++j){
            int k = GAO[(i&(-i))];
            g[j][i] = g[j][i-(1<<k)] + cost[j][k];
        }
    }

    for(int i = 0;i < (1<<id);++i){

        for(int j = 0;j < id;++j){
            if(i&(1<<j))continue;
            f[i|(1<<j)] = min(f[i|(1<<j)],f[i]+g[j][i|(1<<j)]);
        }
    }

    printf("%d",f[(1<<id)-1]+1);		// 可以想一下为什么要+1

    return 0;
}
/**

*/

F

题意

  • 给一个 N × N N\times N N×N S S S 矩阵,其中 S i , j S_{i,j} Si,j 为一个美丽值。
    你需要选择一些地方,使得每一个 2 × 2 2\times2 2×2 的子矩阵中都有且仅有两个被选择的点。
    你的收益为选择所有地方的美丽值。
    求最大收益。
  • 2 ≤ N ≤ 1000 2\le N\le 1000 2N1000

思路(改)

  • 看下图:
    在这里插入图片描述
  • 也就是说:对于这种情况,每一行要么全选奇数位置的,要么全选偶数位置的。
    竖着的情况也类似,每一列要么全选奇数位置,要么全选偶数位置。

代码

  • 注意用快读, s c a n f scanf scanf 直接 T T T 的不要不要的…
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e3+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

namespace Fast_IO{ ///orz laofu
    const int MAXL((1 << 18) + 1);int iof, iotp;
    char ioif[MAXL], *ioiS, *ioiT, ioof[MAXL],*iooS=ioof,*iooT=ioof+MAXL-1,ioc,iost[55];
    char Getchar(){
        if (ioiS == ioiT){
            ioiS=ioif;ioiT=ioiS+fread(ioif,1,MAXL,stdin);return (ioiS == ioiT ? EOF : *ioiS++);
        }else return (*ioiS++);
    }
    void Write(){fwrite(ioof,1,iooS-ioof,stdout);iooS=ioof;}
    void Putchar(char x){*iooS++ = x;if (iooS == iooT)Write();}
    inline int read(){
        int x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    inline long long read_ll(){
        long long x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }
    /**
    #define lll __int128
    inline lll read_lll(){
        lll x=0;for(iof=1,ioc=Getchar();(ioc<'0'||ioc>'9')&&ioc!=EOF;)iof=ioc=='-'?-1:1,ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(x=0;ioc<='9'&&ioc>='0';ioc=Getchar())x=(x<<3)+(x<<1)+(ioc^48);return x*iof;
    }*/
    template <class Int>void Print(Int x, char ch = '\0'){
        if(!x)Putchar('0');if(x<0)Putchar('-'),x=-x;while(x)iost[++iotp]=x%10+'0',x/=10;
        while(iotp)Putchar(iost[iotp--]);if (ch)Putchar(ch);
    }
    void Getstr(char *s, int &l){
        for(ioc=Getchar();ioc==' '||ioc=='\n'||ioc=='\t';)ioc=Getchar();
		if(ioc==EOF)exit(0);
        for(l=0;!(ioc==' '||ioc=='\n'||ioc=='\t'||ioc==EOF);ioc=Getchar())s[l++]=ioc;s[l] = 0;
    }
    void Putstr(const char *s){for(int i=0,n=strlen(s);i<n;++i)Putchar(s[i]);}
} // namespace Fast_IO
using namespace Fast_IO;

int aa[MAX][MAX];

int main()
{
    int n;n = read();
    for(int i = 1;i <= n;++i)
    for(int j = 1;j <= n;++j){
        aa[i][j] = read();
    }

    int ans1 = 0;
    int ans2 = 0;
    for(int i = 1;i <= n;++i){
        int ji=0,ou=0;
        for(int j = 1;j <= n;++j){
            if(j&1)ji+=aa[i][j];
            else ou+=aa[i][j];
        }
        ans1 += max(ji,ou);
    }
    for(int i = 1;i <= n;++i){
        int ji=0,ou=0;
        for(int j = 1;j <= n;++j){
            if(j&1)ji+=aa[j][i];
            else ou+=aa[j][i];
        }
        ans2 += max(ji,ou);
    }
    printf("%d",max(ans1,ans2));
    return 0;
}
/**

*/

G

题意

  • N N N 个点,每一次循环有 K K K 个操作。
    i i i 个操作:交换位置为 a i a_i ai b i b_i bi 的点。
    问你无穷时间后, 每个点能到多少个不同的位置
  • 2 ≤ N ≤ 1 0 5 2\le N\le 10^5 2N105
    1 ≤ K ≤ 2 e 5 1\le K\le 2e5 1K2e5

思路

  • 经过一次循环,就相当于获得了一个置换。
    把置换连边,得到了一个置换群的图。去跑 d f s dfs dfs染色,每一个置换环的颜色相同。
  • 然后从头操作,设 M [ i ] [ j ] M[i][j] M[i][j] 表示颜色为 i i i 的点能否跑到位置 j j j
    然后对于每一次操作,都去更新这个数组即可。

代码

  • 时间复杂度: O ( N + K ) O(N+K) O(N+K)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 2e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

int per[MAX];
int aa[MAX],bb[MAX];
int nxt[MAX];
vector<int>V[MAX];
int id;
int col[MAX];
void dfs(int x){
    for(auto it : V[x]){
        if(!col[it]){
            col[it] = id;
            dfs(it);
        }
    }
}
int shu[MAX];
unordered_map<int,unordered_map<int,bool> >M;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);

    for(int i = 1;i <= n;++i)per[i] = i;

    for(int i = 1;i <= m;++i){
        scanf("%d%d",&aa[i],&bb[i]);
        swap(per[aa[i]],per[bb[i]]);
    }
    for(int i = 1;i <= n;++i){
        nxt[i] = per[i];
        //show(nxt[i]);
        V[i].push_back(nxt[i]);
    }
    for(int i = 1;i <= n;++i){
        if(!col[i]){
            col[i]=++id;
            dfs(i);
        }
    }

    for(int i = 1;i <= n;++i)per[i] = i,M[col[i]][i] = true,shu[col[i]]++;

    for(int i = 1;i <= m;++i){
        if(!M[col[per[aa[i]]]][bb[i]]){
            M[col[per[aa[i]]]][bb[i]] = true;
            shu[col[per[aa[i]]]]++;
        }
        if(!M[col[per[bb[i]]]][aa[i]]){
            M[col[per[bb[i]]]][aa[i]] = true;
            shu[col[per[bb[i]]]]++;
        }
        swap(per[aa[i]],per[bb[i]]);
    }


    for(int i = 1;i <= n;++i){
        printf("%d\n",shu[col[i]]);
    }

    return 0;
}
/**
5 0
*/

H

题意

  • 给一个长度为 N N N 的字符串 S S S
    给定 Q Q Q 个询问,每次询问若 [ a i , b i ] [a_i,b_i] [ai,bi] 不染色,完成字符串的最小次数。
    染色操作:字母越小,表示亮度越高。每一次染色可以选择一个区间,然后把区间的颜色都涂成 k k k。要求是区间内原本有的颜色必须 < k <k <k 或者还没染色。
  • 比如可以 [ . . . ] → [ A A . ] → [ A B B ] [...]\rightarrow [AA.] \rightarrow [ABB] [...][AA.][ABB]
    1 ≤ N ≤ 1 0 5 1\le N\le 10^5 1N105
    1 ≤ Q ≤ 1 0 5 1\le Q\le 10^5 1Q105

思路

  • 中间一段不涂,等价于涂区间为 [ 1 , a i − 1 ] [1,a_i-1] [1,ai1] 的次数 + 涂区间尾 [ b i + 1 , N ] [b_i+1,N] [bi+1,N] 的次数。
  • 涂区间为 [ 1 , x ] [1,x] [1,x] 的答案怎么算呢?因为颜色重的可以覆盖颜色轻的,比如 [ . . . . ] → [ A A A A ] → [ A B B A ] [....]\rightarrow[AAAA]\rightarrow[ABBA] [....][AAAA][ABBA]
    我们可以想成一个这样的题目:
    在这里插入图片描述
  • 然后就变成了一个单调栈的问题了。
    单调栈应该都会吧(

代码

  • 时间复杂度: O ( N + Q ) O(N+Q) O(N+Q)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

char aa[MAX];
int pp[MAX],ss[MAX];
stack<int>S;
void pre(int t){
    while(!S.empty())S.pop();
    S.push(-INF);
    int res = 0;
    for(int i = 1;i <= t;++i){
        while(aa[i] < S.top()){
            S.pop();
        }
        if(aa[i] > S.top()){
            S.push(aa[i]);
            res++;
        }
        pp[i] = res;
    }
}
void suf(int s,int t){
    while(!S.empty())S.pop();
    S.push(-INF);
    int res = 0;
    for(int i = t;i >= s;--i){
        while(aa[i] < S.top()){
            S.pop();
        }
        if(aa[i] > S.top()){
            S.push(aa[i]);
            res++;
        }
        ss[i] = res;
    }
}
int main()
{
    int n,q;
    scanf("%d%d%*c",&n,&q);
    scanf("%s%*c",aa+1);
    pre(n);
    suf(1,n);

    while(q--){
        int ta,tb;
        scanf("%d%d",&ta,&tb);
        printf("%d\n",pp[ta-1] + ss[tb+1]);
    }
    return 0;
}
/**
8 3
ABBAABCB
3 6
1 4
1 8
*/

I

题意

  • N N N 头牛,高度为 a i a_i ai
    N N N 个房子,高度为 b i b_i bi
    牛必须住到高度不低于它的房子。
    每个房子只能住一头牛。
    问你所有牛都住进去的方案数。
  • 1 ≤ N ≤ 20 1\le N\le 20 1N20

思路

  • 一开始状压 d p dp dp 要么 T L E TLE TLE 要么 M L E MLE MLE
    只要 两个数组降序排序。
    考虑 d p [ i ] dp[i] dp[i] 表示前 i i i 头牛都住进去的合法方案数。
    y a n [ i ] = j yan[i]=j yan[i]=j 表示第 i i i 头牛最矮住的进去第 j j j 个房子。
    那么第 i i i 头牛住进去的方案数多少呢? ( y a n [ i ] − ( i − 1 ) ) (yan[i]-(i-1)) (yan[i](i1))
    那么就得到了石子: d p [ i ] = d p [ i − 1 ] × ( y a n [ i ] − ( i − 1 ) ) dp[i]=dp[i-1]\times(yan[i]-(i-1)) dp[i]=dp[i1]×(yan[i](i1))

代码

  • 时间复杂度:可以到 O ( N log ⁡ N ) O(N\log N) O(NlogN)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e7+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;


int aa[25],bb[25];
ll dp[25];
ll yan[25];
int main()
{
    int n;scanf("%d",&n);
    for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);
    for(int i = 1;i <= n;++i)scanf("%d",&bb[i]);
    sort(aa+1,aa+1+n,greater<int>());
    sort(bb+1,bb+1+n,greater<int>());

    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= n;++j){
            if(aa[i] <= bb[j])yan[i] = j;
            else break;
        }
    }
    dp[0] = 1;
    for(int i = 1;i <= n;++i){
        dp[i] = dp[i-1] * (yan[i] - (i-1));
    }
    printf("%lld",dp[n]);
    return 0;
}
/**
4
1 1 1 1
1 1 1 1
*/

J

题意

  • ⊕ L M F ( i ) \oplus LMF(i) LMF(i)
    其中 L M F ( i ) LMF(i) LMF(i) 表示 i i i 的最小质因子。
  • 2 ≤ N ≤ 1 0 7 2\le N\le 10^7 2N107

思路

  • 欧拉筛就是通过每个数的最小质因子来实现线性筛的。

代码

  • 时间复杂度: O ( N ) O(N) O(N)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e7+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

bool vis[MAX];
int prime[MAX];
int cnt;
int ans = 0;
void shai(int n){
    for(int i = 2;i <= n;++i){
        if(!vis[i])prime[++cnt] = i,ans^=i;
        for(int j = 1;j <= cnt && i * prime[j] <= n;++j){
            vis[i*prime[j]] = 1;
            //show(i*prime[j],prime[j]);
            ans^=prime[j];
            if(i%prime[j] == 0)break;
        }
    }
}
int main()
{
    int n;scanf("%d",&n);
    shai(n);
    printf("%d",ans);
    return 0;
}
/**
2 2
1 2
00
00
2 1
1 1
1
*/

K

题意

  • N N N 个数,每个数有编号 B i B_i Bi
    需要划分成最多数量的区间,使得奇数编号的区间,区间内的编号和为偶数;偶数编号的区间,区间内的编号和为奇数。
  • 2 ≤ N ≤ 1 0 3 2\le N\le 10^3 2N103

思路

  • 只要考虑你现在有多少个奇数和多少个偶数编号即可。
    每次可以取两个点合并,就三种情况。
    记忆化,能否获得 奇数点有 x x x 个,偶数点有 y y y 个的情况。
    简单的 b f s bfs bfs 即可。
    然后合法的答案: x = y x=y x=y 或者 x + 1 = y x+1=y x+1=y
  • 然后就可以不用分类讨论了

代码

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e7+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

int shu[2];
bool can[1050][1050];
int ans = 1;
void bfs(int s,int t){
    queue<pair<int,int> >Q;
    Q.push(make_pair(s,t));
    while(!Q.empty()){
        int x = Q.front().first;
        int y = Q.front().second;
        //show(x,y);
        Q.pop();
        if(x>=2 && !can[x-2][y+1]){
            can[x-2][y+1] = true;
            Q.push(make_pair(x-2,y+1));
        }
        if(x && y && !can[x][y-1]){
            can[x][y-1] = true;
            Q.push(make_pair(x,y-1));
        }
        if(y>=2 && !can[x][y-1]){
            can[x][y-1] = true;
            Q.push(make_pair(x,y-1));
        }
        if(x == y){
            ans = max(ans,x+y);
        }else if(x + 1 == y){
            ans = max(ans,x+y);
        }
    }
}

int main()
{
    int n;scanf("%d",&n);
    for(int i = 1;i <= n;++i){
        int t;scanf("%d",&t);
        shu[(t&1)]++;
    }
    can[shu[1]][shu[0]] = true;
    bfs(shu[1],shu[0]);
    printf("%d",ans);

    return 0;
}
/**
4
1 1 1 1
1 1 1 1
*/
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值