【解题报告】2021牛客寒假算法基础集训营 3


A 模数的世界 | 数论 + 构造

时间复杂度 O ( T k ) O(Tk) O(Tk)

  • 【题目描述】
    T T T 组样例。每组给定 a , b , p a,b,p a,b,p,要求构造出一对数字 t a , t b ta,tb ta,tb
    满足 t a ≡ a ( m o d p ) ta\equiv a\pmod p taa(modp) t b ≡ b ( m o d p ) tb\equiv b\pmod p tbb(modp),且 gcd ⁡ ( t a , t b )   %   p \gcd(ta,tb)\ \%\ p gcd(ta,tb) % p 最大
  • 【数据范围】
    T ≤ 1 0 6 T\le 10^6 T106,
    0 ≤ a , b < p < 2 × 1 0 4 0\le a,b<p<2\times10^4 0a,b<p<2×104,其中 p p p 一定是素数
    0 ≤ t a , t b ≤ 2 63 − 1 0\le ta,tb\le 2^{63}-1 0ta,tb2631
  • 【思路】
    (1)根据打表易得,如果不是 a = 0 且 b = 0 a=0且b=0 a=0b=0 的话, gcd ⁡ ( t a , t b )   %   p \gcd(ta,tb)\ \%\ p gcd(ta,tb) % p 最大值为 p − 1 p-1 p1
    (2)我们作一个简单构造: t a = p ( x x x ) + a ta=p(xxx)+a ta=p(xxx)+a t a = ( p − 1 ) ( x x x ) ta=(p-1)(xxx) ta=(p1)(xxx)
    容易构造出来 t a = ( p − a ) ( p − 1 ) , t b = ( p − b ) ( p − 1 ) ta=(p-a)(p-1),tb=(p-b)(p-1) ta=(pa)(p1),tb=(pb)(p1)
    (3)但是有可能 gcd ⁡ ( t a , t b ) % p ≠ p − 1 \gcd(ta,tb)\%p\ne p-1 gcd(ta,tb)%p=p1,咋办呢?我们每次给 t a ta ta 增大一些,并且还能继续保持 t a ≡ a ( m o d p ) ta\equiv a\pmod p taa(modp) 以及 t a ≡ 0 ( m o d p − 1 ) ta\equiv 0\pmod{p-1} ta0(modp1),我们增大多少呢?增大 p ( p − 1 ) p(p-1) p(p1) 即可。
int main()
{
    
    
    int T;scanf("%d",&T);
    while(T--){
    
    
        ll a,b,p;
        scanf("%lld%lld%lld",&a,&b,&p);
        ll ta = (p - a) * (p - 1);
        ll tb = (p - b) * (p - 1);
        if(a == 0 && b == 0){
    
    
            puts("0 0 0");
            continue;
        }
        while(1){
    
    
            if(__gcd(ta,tb) % p == p-1){
    
    
                printf("%d %d %d\n",p-1,ta,tb);
                break;
            }
            ta += p*(p - 1);
        }
    }
    return 0;
}

B 内卷 | 数据结构 + 贪心

时间复杂度 O ( 5 n log ⁡ ( 5 n ) ) O(5n\log(5n)) O(5nlog(5n))

  • 【题目描述】
    每个人的考试有五个等第期望得分: A i , B i , C i , D i , E i A_i,B_i,C_i,D_i,E_i Ai,Bi,Ci,Di,Ei
    要求获得 A A A 等的人最多不超过 k k k 个人。
    问你每个人选择一个等第期望分数后,
    max ⁡ { 每 个 人 选 择 的 等 第 期 望 分 } − min ⁡ { 每 个 人 选 择 的 等 第 期 望 分 } \max\{每个人选择的等第期望分\}-\min\{每个人选择的等第期望分\} max{ }min{ } 最小是多少。
  • 【数据范围】
    1 ≤ k ≤ n ≤ 1 0 5 1\le k\le n\le 10^5 1kn105
    1 ≤ E i ≤ D i ≤ C i ≤ B i ≤ A i ≤ 1 0 9 1\le E_i \le D_i \le C_i \le B_i \le A_i \le 10^9 1EiDiCiBiAi109
  • 【思路】
    (1)首先默认每个人都是最低分 E i E_i Ei
    (2)怎么改变答案?让其中分数最低的人分数更高些,即选择高一等地的期望分数
    (3)重复这个过程,记录每次的 max ⁡ − min ⁡ \max-\min maxmin,直到选择 A A A 等的人超过 k k k 或者分数最低的人都是 A A A 等了为止。
    (4)怎么快速记录当前所选人的最小分数?用小顶堆
const int MAX = 1e5+50;

int aa[MAX][10];
struct node{
    
    
    int a,b,val;
    bool operator <(const node &ND)const{
    
    
        return val > ND.val;
    }
};
priority_queue<node>Q;
int main()
{
    
    
    int n,k;
    scanf("%d%d",&n,&k);
    int mx = 0;
    int cnt = 0;
    for(int i = 1;i <= n;++i){
    
    
        for(int j = 1;j <= 5;++j)
            scanf("%d",&aa[i][j]);
        Q.push({
    
    i,5,aa[i][5]});
        mx = max(mx,aa[i][5]);
    }
    int res = mx - Q.top().val;
    while(1){
    
    
        node t = Q.top();
        Q.pop();
        Q.push({
    
    t.a,t.b-1,aa[t.a][t.b-1]});
        if(t.b - 1 == 1)cnt++;
        if(t.b - 1 == 0)break;
        if(cnt > k)break;
        mx = max(mx,aa[t.a][t.b-1]);
        res = min(res,mx - Q.top().val);
    }
    printf("%d",res);
    return 0;
}

C 重力坠击 | 搜索 或 状压DP

搜索 O ( n × 6 4 k ) O(n\times64^k) O(n×64k)
状压 O ( k × 2 n × 2 n ) O(k\times2^n\times2^n) O(k×2n×2n)
我这里是状压做法。

  • 【题目描述】
    二维平面,有 n n n 个敌人,每个敌人有坐标 x i , y i x_i,y_i xi,yi,有半径 r i r_i ri
    你有 k k k 次攻击机会,每次选择一个整数坐标,然后攻击半径 R R R
    问你至多摧毁多少个敌人
  • 【数据范围】
    1 ≤ n ≤ 10 1\le n\le 10 1n10
    1 ≤ k ≤ 3 1\le k\le 3 1k3
    1 ≤ r i , R ≤ 7 1\le r_i,R\le 7 1ri,R7
    0 ≤ ∣ x i ∣ , ∣ y i ∣ ≤ 7 0\le |x_i|,|y_i|\le7 0xi,yi7
  • 【思路】
    (1)状态压缩,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示用 j j j 次攻击后,能打死的敌人集合为 i i i
    (2)状态转移:如果能一击打死敌人集合 j − i j-i ji j & i = i j\&i=i j&i=i,那么 d p [ j ] [ q ]   ∣  ⁣ = d p [ i ] [ q + 1 ] dp[j][q] \ |\!= dp[i][q+1] dp[j][q] =dp[i][q+1]
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e6+50;

struct node{
    
    
    int x,y,r;
}aa[20];
bool dp[2100][5];
int shu[2100];
bool can[2100];
bool ok(int x,int y,int r,int k){
    
    
    int dx = abs(x - aa[k].x);
    int dy = abs(y - aa[k].y);
    int dis = dx * dx + dy * dy;
    return dis <= (r + aa[k].r) * (r + aa[k].r);
}
void init(int n,int x,int r){
    
    
    for(int i = 0;i < x;++i){
    
    
        int t = i;
        int cnt = 0;
        while(t){
    
    
            cnt += t%2;
            t/=2;
        }
        shu[i] = cnt;
    }
    for(int i = -7;i <= 7;++i)
    for(int j = -7;j <= 7;++j){
    
    
        int tmp = 0;
        for(int k = 0;k < n;++k){
    
    
            tmp = tmp * 2;
            if(ok(i,j,r,k))tmp++;
        }
        can[tmp] = 1;
        for(int k = 0;k < x;++k){
    
    		/// 杀死敌人的集合的子集也要枚举
            if((k & tmp) == k)can[k] = 1;
        }
    }

}
int main()
{
    
    
    int n,k,R;
    scanf("%d%d%d",&n,&k,&R);
    for(int i = 0;i < n;++i){
    
    
        scanf("%d%d%d",&aa[i].x,&aa[i].y,&aa[i].r);
    }
    init(n,(1<<n),R);
    for(int i = 0;i <= k;++i)
        dp[0][i] = 1;

    for(int i = 0;i < (1<<n);++i){
    
    
        for(int j = i;j < (1<<n);++j){
    
    
            if((i & j) == i){
    
    
                if(can[j-i]){
    
    
                    for(int p = 1;p <= k;++p)
                        dp[j][p] |= dp[i][p-1];
                }
            }
        }
    }
    int mx = 0;
    for(int i = 0;i < (1<<n);++i){
    
    
        if(dp[i][k]){
    
    
            mx = max(mx,shu[i]);
        }
    }
    printf("%d",mx);
    return 0;
}

D Happy New Year! | 暴力 (签到)

  • 【题意】
    求出最小的数位和等于 n n n 的数字
  • 【数据范围】
    2021 ≤ n ≤ 2030 2021\le n\le 2030 2021n2030
  • 【思路】
    暴力即可,也不用找规律。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
bool check(int s,int x){
    
    
    int cnt = 0;
    while(s){
    
    
        cnt += s%10;
        s /= 10;
    }
    return cnt == x;
}

int main()
{
    
    
    int n;cin >> n;
    int st = n + 1;
    int cnt = 0;
    while(n){
    
    
        cnt += n%10;
        n /= 10;
    }
    while(1){
    
    
        if(check(st,cnt)){
    
    
            cout << st;
            break;
        }
        st++;
    }
    return 0;
}

E 买礼物 | 数据结构 (线段树+链表)

时间复杂度: O ( n log ⁡ n + q log ⁡ q ) O(n\log n+q\log q) O(nlogn+qlogq)

  • 【题意】
    n n n 个柜子,每个柜子有礼物编号 a i a_i ai
    q q q 个询问
    如果询问 1   x 1\ x 1 x,表示第 x x x 个柜子的礼物被买走
    如果询问 2   a   b 2\ a\ b 2 a b,表示询问在区间 [ a , b ] [a,b] [a,b] 的柜子中,是否有两个柜子的礼物编号相同。
  • 【数据范围】
    1 ≤ n , q ≤ 5 × 1 0 5 1\le n,q\le 5\times10^5 1n,q5×105
    1 ≤ a i ≤ 1 0 6 1\le a_i\le 10^6 1ai106
    礼物买走操作均合法。
  • 【思路】
    (1)我们把相同编号的礼物像链表一样,串起来
    对于某个位置 i i i ,我们记录前驱 l a s t [ i ] last[i] last[i] 和后继 n x t [ i ] nxt[i] nxt[i]
    前驱指的是往前最近的位置 l a s t [ i ] last[i] last[i] ,使得 a l a s t [ i ] = a i a_{last[i]}=a_i alast[i]=ai,若不存在则 l a s t [ i ] = 0 last[i]=0 last[i]=0
    后继指的是往后最近的位置 n x t [ i ] nxt[i] nxt[i] ,使得 a n x t [ i ] = a i a_{nxt[i]}=a_i anxt[i]=ai,若不存在则 n x t [ i ] = n + 1 nxt[i]=n+1 nxt[i]=n+1
    (2)如果我们要查询 2   a   b 2\ a\ b 2 a b,即找到区间里是否有两个相同编号的礼物,即找到区间里是否有一个 n x t [ i ] nxt[i] nxt[i] 满足 n e x [ i ] ≤ b nex[i]\le b nex[i]b(或者同理的, l a s t [ i ] ≥ a last[i]\ge a last[i]a)
    (3)因为我们预处理出如果没有后继,则 n x t [ i ] = n + 1 nxt[i]=n+1 nxt[i]=n+1,所以只要寻找区间内 n x t [ i ] nxt[i] nxt[i] 的最小值即可,就是一个线段树题了。
    (4)买走某个礼物,即让一个链的中间断开,然后两端再连上,即:
    n x t [ l a s t [ i ] ] = n x t [ i ] l a s t [ n x t [ i ] ] = l a s t [ i ] n x t [ i ] = n + 1 l a s t [ i ] = 0 nxt[last[i]]=nxt[i]\\last[nxt[i]]=last[i]\\nxt[i]=n+1\\last[i]=0 nxt[last[i]]=nxt[i]last[nxt[i]]=last[i]nxt[i]=n+1last[i]=0
const int MAX = 1e6+50;

int aa[MAX];
int pos[MAX];
int last[MAX];
int nxt[MAX];
const int MAX_LEN = MAX;

int seg_tree[MAX_LEN << 2];
int Lazy[MAX_LEN << 2];
//从下往上更新 节点
void push_up (int root) {
    
    
	seg_tree[root] = min(seg_tree[root << 1], seg_tree[root << 1 | 1]);  	//最小值改min
}
//从上向下更新,左右孩子
void push_down (int root, int L, int R) {
    
    
	if(Lazy[root]){
    
    
		Lazy[root << 1] += Lazy [root];
		Lazy[root << 1 | 1] += Lazy[root];
		int mid = (L + R) >> 1;
		seg_tree[root << 1] +=  Lazy[root] * (mid - L + 1);
		seg_tree[root << 1 | 1] += Lazy[root] * (R - mid);
		Lazy[root] = 0;
	}
}
//建树
void build (int root, int L, int R) {
    
    
	Lazy[root] = 0;
	if(L == R) {
    
    
		seg_tree[root] = nxt[L];
		return ;
	}
	int mid = (L + R) >> 1;
	build(root << 1, L, mid);
	build(root << 1 | 1, mid + 1, R);
	push_up(root);
}

//区间查询
int query (int root, int L, int R, int LL, int RR) {
    
    
	if (LL <= L && R <= RR) return seg_tree[root];
	push_down(root, L, R); 	//每次访问都去检查Lazy 标记
	int Ans = INF;
	int mid = (L + R) >> 1;
	if(LL <= mid) Ans = min(Ans, query(root << 1, L, mid, LL, RR));	//最小值改min
	if(RR > mid) Ans = min(Ans, query(root << 1 | 1, mid + 1, R, LL, RR)); //最小值改min
	return Ans;
}
//单点修改 可以改为某值,或者+-某值
void update(int root, int L, int R, int pos, int val) {
    
    
	if(L == R){
    
    
		seg_tree[root] = val;	//点直接变为某值
		return ;
	}
	int mid = (L + R) >> 1;
	if(pos <= mid) update(root << 1, L, mid, pos, val);
	else update(root << 1 | 1, mid + 1, R, pos, val);
	push_up(root);
}
int main()
{
    
    
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i = 1;i <= n;++i){
    
    
        scanf("%d",&aa[i]);
        if(pos[aa[i]]){
    
    
            last[i] = pos[aa[i]];
            nxt[pos[aa[i]]] = i;
        }
        else last[i] = 0;

        pos[aa[i]] = i;
        nxt[i] = n + 1;
    }
    build(1,1,n);
    for(int i = 0;i < q;++i){
    
    
        int op;scanf("%d",&op);
        if(op == 1){
    
    
            int x;scanf("%d",&x);
            nxt[last[x]] = nxt[x];
            last[nxt[x]] = last[x];
            update(1,1,n,x,n+1);
            update(1,1,n,last[x],nxt[x]);
            nxt[x] = n + 1;
            last[x] = 0;
        }else{
    
    
            int a,b;scanf("%d%d",&a,&b);
            int qu = query(1,1,n,a,b);
            if(qu <= b)puts("1");
            else puts("0");
        }
    }
    return 0;
}

F 匹配串 | 字符串

时间复杂度: O ( n ∣ S i ∣ ) O(n|S_i|) O(nSi)
虽然看似很大,但是思路和标程差不多。并且题目保证输入串的总长 ≤ 1 0 6 \le 10^6 106

  • 【题意】
    n n n 个模式串。模式串指的是中间包含 # \# # 的,其他都是小写字母的串。
    一个模式串的匹配串指的是该模式串的 # \# # 位置替换为空或者任意长度的小写字母串。
    问你,同时满足这 n n n 个匹配串的模式串的个数为多少?
    如果为 ∞ \infin 种,输出 -1。
  • 【数据范围】
    1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106
    题目保证输入串的总长 ≤ 1 0 6 \le 10^6 106
  • 【思路】
    (1)容易想出来,要么匹配串的个数为 0 0 0 ,要么为 ∞ \infin 个。
    (2)我们把所有匹配串的最长不包含 # \# # 的那一个前缀拿出来,把所有匹配串的最长不包含 # \# # 的那一个后缀拿出来。
    (3)每一个串和那个前缀和后缀去对应。前缀正着比,后缀倒着比。如果有一个是 # \# #,那就直接跳掉,因为这个可以任意填充。如果两个串这一位的字母不同了,那么就无法构造出匹配串。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e6+50;

string ss[MAX];
bool sam(int x,int y,bool rear){
    
    
    bool flag = true;
    if(!rear){
    
    
        for(int i = 0;i < ss[y].size();++i){
    
    
            if(ss[y][i] == '#')break;
            if(ss[x][i] == '#')break;
            if(ss[x][i] != ss[y][i]){
    
    
                flag = false;
                break;
            }
        }
    }else{
    
    
        for(int i = 0;i < ss[y].size();++i){
    
    
            if(ss[y][ss[y].size()-1-i] == '#')break;
            if(ss[x][ss[x].size()-1-i] == '#')break;
            if(ss[x][ss[x].size()-1-i] != ss[y][ss[y].size()-1-i]){
    
    
                flag = false;
                break;
            }
        }
    }
    return flag;
}
int main()
{
    
    
    int n;scanf("%d",&n);
    bool flag = true;
    int mx1 = -1,mx2 = -1;
    int id1 = 0,id2 = 0;
    for(int i = 1;i <= n;++i){
    
    
        cin >> ss[i];
        for(int j = 0;j < ss[i].size();++j){
    
    
            if(ss[i][j] == '#'){
    
    
                if(mx1 < j-1){
    
    
                    mx1 = j-1;
                    id1 = i;
                }
                break;
            }
        }
        for(int j = ss[i].size()-1;j >= 0;--j){
    
    
            if(ss[i][j] == '#'){
    
    
                if(mx2 < (int)ss[i].size()-1-j){
    
    
                    mx2 = ss[i].size()-1-j;
                    id2 = i;
                }
                break;
            }
        }
    }
    for(int i = 1;flag && i <= n;++i){
    
    
        if(!sam(i,id1,0))flag = false;
        if(!sam(i,id2,1))flag = false;
    }
    if(flag)puts("-1");
    else puts("0");
    return 0;
}

G 糖果 | 并查集

  • 【题意】
    n n n 个小盆友,第 i i i 个小盆友想要 a i a_i ai 个糖
    m m m 对好朋友,好朋友的好朋友不一定是你的好朋友。
    每个人拿糖的个数不得少于 a i a_i ai,也不得少于他所有朋友拿到的糖。
  • 【数据范围】
    1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106
    0 ≤ m ≤ 1 0 6 0\le m\le 10^6 0m106
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109
  • 【思路】
    跑并查集。每个人在该人所有朋友糖里面拿最大值即可。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e6+50;

ll aa[MAX];
vector<int>V[MAX];
int bh[MAX];
ll mx[MAX];
void dfs(int x,int c){
    
    
    bh[x] = c;
    for(auto it : V[x]){
    
    
        if(bh[it])continue;
        dfs(it,c);
    }
}
int main()
{
    
    
    int n,m;
    scanf("%d%d",&n,&m);
    ll sum = 0;
    for(int i = 1;i <= n;++i)scanf("%lld",&aa[i]);
    for(int i = 1;i <= m;++i){
    
    
        int ta,tb;scanf("%d%d",&ta,&tb);
        V[ta].push_back(tb);
        V[tb].push_back(ta);
    }
    int cnt = 0;
    for(int i = 1;i <= n;++i){
    
    
        if(!bh[i])dfs(i,++cnt);
        mx[bh[i]] = max(mx[bh[i]],aa[i]);
    }
    for(int i = 1;i <= n;++i){
    
    
        sum += mx[bh[i]];
    }

    printf("%lld",sum);
    return 0;
}

H 数字串 | 贪心

  • 【题意】
    a a a 1 1 1 ,依次类推 z z z 26 26 26
    给定一个字符串,请你给出另一个不同的字符串,使得他们的字符串转成数字串都相同。
    比如 c w c = c b c c = 3233 cwc=cbcc=3233 cwc=cbcc=3233
  • 【数据范围】
    给定字符串长度 ≤ 1 0 6 \le 10^6 106
    输出字符串长度必须 ≤ 2 × 1 0 6 \le 2\times 10^6 2×106
  • 【思路】
    贪心。
    如果有能拆成两个的你就拆,分别从 11 ∼ 19 11\sim 19 1119 以及 21 ∼ 26 21\sim 26 2126
    如果有能和的两个的你就和,分别为 a a ∼ a j aa\sim aj aaaj 以及 b a ∼ b f ba\sim bf babf
    如果匹配完了没有变动的,就是无法给出,输出 − 1 -1 1
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 2e6+50;

char ss[MAX];
bool use[MAX];
int cnt;
char ans[MAX];
int main()
{
    
    
    scanf("%s",ss);
    bool upd = false;
    for(int i = 0;i < strlen(ss);++i){
    
    
        int t = ss[i] - 'a' + 1;
        if(t >= 11 && t <= 19){
    
    
            upd = true;
            char ca = 'a';
            char cb = 'a' + t - 10 - 1;
            ans[++cnt] = ca;
            ans[++cnt] = cb;
        }else if(t >= 21 && t <= 26){
    
    
            upd = true;
            char ca = 'b';
            char cb = 'a' + t - 20 - 1;
            ans[++cnt] = ca;
            ans[++cnt] = cb;
        }else if(i){
    
    
            if(ss[i-1] == 'a' && use[i-1] == 0 && t <= 9){
    
    
                upd = true;
                char ca = 'a' + 10 + t - 1;
                ans[cnt] = ca;
                use[i-1] = use[i] = 1;
            }else if(ss[i-1] == 'b' && use[i-1] == 0 && t <= 6){
    
    
                upd = true;
                char ca = 'a' + 20 + t - 1;
                ans[cnt] = ca;
                use[i-1] = use[i] = 1;
            }else{
    
    
                ans[++cnt] = ss[i];
            }
        }else{
    
    
            ans[++cnt] = ss[i];
        }
    }
    if(!upd)puts("-1");
    else{
    
    
        for(int i = 1;i <= cnt;++i)
            printf("%c",ans[i]);
    }
    return 0;
}

I 序列的美观度 | DP

时间复杂度: O ( n ) O(n) O(n)

  • 【题意】
    给定一个长度为 n n n 的序列 S S S
    如果 ∃   i ∈ [ 1 , n − 1 ] \exist\ i\in[1,n-1]  i[1,n1],满足 S i = S i + 1 S_i=S_{i+1} Si=Si+1,则 S S S 序列有一点美观度。
    问你, S S S 的所有子序列中,美观度最大的为多少。
    子序列:原序列删除一些位置(或不删除)后产生的序列
  • 【数据范围】
    2 ≤ n ≤ 1 0 6 2\le n\le 10^6 2n106
    1 ≤ s i ≤ 1 0 6 1\le s_i\le 10^6 1si106
  • 【思路】
    (1)明显的动态规划。设 d p [ i ] dp[i] dp[i] 表示末尾为数字 i i i ,能得到的最大美观度
    (2)转移: d p ( S i ) = max ⁡ { d p ( S i ) + 1 , d p ( S i − 1 ) } dp(S_i)=\max\{dp(S_i)+1,dp(S_{i-1})\} dp(Si)=max{ dp(Si)+1,dp(Si1)}
    如果你这一位选 S i S_i Si ,那就是上一次末尾选择 S i S_i Si的美观度+1,或者是末尾选择了上一位 S i − 1 S_{i-1} Si1 的美观度。
    其实 d p ( S i − 1 ) dp(S_{i-1}) dp(Si1) 是一个前面状态的 m a x max max 值。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e6+50;

int dp[MAX];
int aa[MAX];
int main()
{
    
    
    int n;scanf("%d",&n);
    memset(dp,-1,sizeof(dp));		/// 注意初始化为 -1
    int ans = 0;
    for(int i = 1;i <= n;++i){
    
    
        scanf("%d",&aa[i]);
        dp[aa[i]] = max(dp[aa[i]]+1,dp[aa[i-1]]);
        ans = max(ans,dp[aa[i]]);
    }
    printf("%d",ans);
    return 0;
}

J 加法和乘法 | 博弈

时间复杂度: O ( n ) O(n) O(n)

  • 【题意】
    桌上有 n n n 张牌,每张牌数字 a i a_i ai
    两人轮流行动。每次每人选择两张 a i 、 a i a_i、a_i aiai,然后选择使用加法或者乘法替换这两张牌。
    即删掉 a i 、 a j a_i、a_j aiaj,用 a i + a j a_i+a_j ai+aj 替换 或用 a i × a j a_i\times a_j ai×aj 替换。
    如果只剩一张牌了,该牌为奇数则先手赢,否则为后手赢。
    问你先手必胜还是后手必胜。
  • 【数据范围】
    1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106
    1 ≤ a i ≤ 1 0 9 1\le a_i\le 10^9 1ai109
  • 【思路】
    (1)易得,我们按照牌的奇偶关系进行思考。枚举每种奇偶关系的加法和乘法
    (2)先手肯定要多删掉偶数,后手肯定要多删掉奇数。
    如果还能操作,先手优先删掉一个偶数(偶 * 偶=偶或者奇+偶=奇);如果没有偶数只能删掉一个奇数(奇 * 奇=奇)
    如果还能操作,后手优先删掉两个奇数(奇 + 奇 = 偶);如果只有一个奇数,那么删掉一个奇数(奇 * 偶 = 偶)
    (3)如果只有偶数,易得怎么操作都是后手赢 (偶 + 偶 = 偶 ,偶 * 偶 = 偶)
    直接贪心枚举情况即可 (正确性感觉还是能保证的)
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
int main()
{
    
    
    int n;scanf("%d",&n);
    int Ji = 0,Ou = 0;
    for(int i = 0;i < n;++i){
    
    
        int t;scanf("%d",&t);
        if(t&1)Ji++;
        else Ou++;
    }
    for(int i = 1;i < n;++i){
    
    
        if(i&1){
    
    
            if(Ou)Ou--;
            else Ji--;
        }else{
    
    
            if(Ji>=2)Ji-=2,Ou++;
            else if(Ji)Ji--;
            else Ou--;
        }
    }
    if(Ji)puts("NiuNiu");
    else puts("NiuMei");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45775438/article/details/113703787
今日推荐