nowcoder Week Contest

Elgina發表於2024-08-10

52

小紅有 \(n\) 個數字 \(a_1, a_2, \dots, a_n\) 和一個空字串 \(s\)。現在她需要執行 \(n\) 次操作:第 \(i\) 次操作需要將 \(a_i\) 按照數位上的相對順序、從左到右的取出並依次插入 \(s\)(在 \(s\) 中不需要連續,但需要保持原有相對順序)。 小紅想要構造一個這樣的字串 \(s\),使得它的字典序是所有合法構造方案中最大的。

2
92 86
9862

小學哥完美的優先佇列 直接給搞成模擬了

#include <bits/stdc++.h>
typedef long long ll;
std::priority_queue< std::pair<char,std::string> > q;
void solve() {
	int n;
	std::cin >> n;
	for(int i = 1; i <= n; i++) {
		std::string s;
		std::cin >> s;
		q.push({s[0],s});
	}
	std::string ans;
	while(!q.empty()) {
		auto [c,s] = q.top(); 
		q.pop();
		ans += c;
		s.erase(0,1);
		if(s != "") {
			q.push({s[0],s});
		}
	}
	std::cout << ans <<'\n';
	return;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int _ = 1;
	//std::cin >> _;
	while(_ --) {
		solve();
	}
	return 0;
}

51

給定一個 n×n 的矩陣,矩陣中的每個元素都是正整數,小紅當前位於左上角 (1, 1),每次可以從 (x, y) 走到 (x+1, y)(x, y+1)(x-1, y)(x, y-1),但不能走出矩陣。小紅希望找到一條到右下角 (n, n) 的路徑,定義路徑權值為路徑上經過的每個點的最大值,求所有路徑中的最小路徑權值。

以為是dp 問題 卻沒發現是 二分最大點值 判斷能否從起點到終點即可

#include <bits/stdc++.h>
typedef long long ll;
int a[1000][1000];
std::vector< std::pair<int,int> > d = {{0,1},{0,-1},{1,0},{-1,0}};
bool vis[600][600];
int n;

void dfs(int x,int y,int w) {
	if(a[x][y] > w || vis[x][y] || vis[n][n]) return;
	vis[x][y] = 1;
	for(auto [i,j] : d) {
		i += x; j += y;
		if(vis[i][j] || i < 1 || i > n || j < 1 || j > n || a[x][y] > w) continue;
		dfs(i,j,w);
	}
}

void solve() {
    std::cin >> n;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            std::cin >> a[i][j];
        }
    }
    if(n == 1) {
    	std::cout << a[1][1] << '\n';
    	return;
    }
    int l = 1, r = 1e9, x = 0;
    while(l <= r) {
    	int mid = (l + r) >> 1;
    	std::memset(vis,0,sizeof(vis));
    	dfs(1,1,mid);
    	if(vis[n][n]) {
    		r = mid - 1;
    		x = mid;
    	}
    	else l = mid + 1;
    }
    std::cout << x << '\n';
    return;
}
 
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _;
    while(_ --) {
        solve();
    }
    return 0;
}

學習到了 遍歷四個方向

std::vector< std::pair<int,int> > d = {{0,1},{0,-1},{1,0},{-1,0}};
for(auto [i,j] : d) {
		i += x; j += y;
		if(vis[i][j] || i < 1 || i > n || j < 1 || j > n || a[x][y] > w) continue;
		dfs(i,j,w);
}

**50 **

小紅有一棵 n 個點的樹,根節點為 1,有一個物塊在根節點上,每次它會等機率隨機移動到當前節點的其中一個子節點,而後等機率隨機傳送到一個同深度節點,若此時它位於葉子節點,則停止移動。 求其移動到子節點的次數的期望值,答案對 998244353 取模。

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int mod = 998244353;

ll ksm(ll a,ll b) {
	ll ans = 1;
	while(b) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}
std::vector<int> e[N];
int a[N],b[N],dep[N];
ll p[N];
ll ans = 0;
int maxdep = -1;
void dfs(int u,int fa) {
	dep[u] = dep[fa] + 1;
	a[dep[u]] += 1;
	maxdep = std::max(maxdep,dep[u]);
	b[dep[u]] += (u != 1 && e[u].size() == 1);// 僅存在一條回邊 說明子葉節點

	for(int i = 0; i < e[u].size(); i++) {
		int v = e[u][i];
		if(v == fa) continue;
		dfs(v,u);
	}
}
void solve() {
	int n;
	std::cin >> n;
	if(n == 1) {
		std::cout << 0 << '\n';
		return;
	}
	for(int i = 1,u,v; i <= n - 1; i++) {
		std::cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	p[1] = 1;
	for(int i = 2; i <= maxdep; i++) {
		p[i] = p[i-1] * (a[i] - b[i]) % mod * ksm(a[i],mod - 2) % mod;
	}
	for(int i = 1; i <= maxdep; i ++) {
		ans += p[i];
		ans %= mod;
	}
	std::cout << ans <<'\n';
	return;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int _ = 1;
	//std::cin >> _;
	while(_ --) {
		solve();
	}
	return 0;
}

一般除法取模轉化為乘法逆元就用費馬小定理就好 \(ksm(a,p-2)\)

49

\(1\le T \le 2 \times 10 ^ 5\) \(1 \le l \le r \le 10^{18}\)

\([l,r]\) 區間所有整數的異或和

異或有結合律,且\(4k,4k+1,4k+2,4k+3\) 異或和為0

#include <bits/stdc++.h>
typedef long long ll;

ll ntum(ll x) {
	// 0 1 2 3 ^ -> 0 
	ll res = 0; // 5 ->(0 ^ 5 ^ 4)
	ll k = (x + 1) % 4; // 次數 
	while(k--) {
		res ^= x;
		x --;
	}
	return res;
}
void solve() {
	ll l,r;
	std::cin >> l >> r;
	std::cout << (ntum(r)^ntum(l-1)) << '\n';
	return;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int _ = 1;
	std::cin >> _;
	while(_ --) {
		solve();
	}
	return 0;
}

希望找到一個最小的正整數\(k\), 使得對於陣列\(a\)中的某些索引\(i\), 滿足 \(a_{i} + a_{i+2k} = 2 \times a_{i+k}\)

\(hash(1,n-2\times k) + hash(2\times k + 1,n) = 2\times hash(k+1,n-2\times k)\)

#include <bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
const int p = 13331;
const int mod = 1e9 + 7;
const int N = 2e6 + 50;
ull P[N];
ull h[N];
ull _hash(int l,int r) {
	if(l < 0 || r < 0) return 0;
	return (h[r] - h[l - 1] * P[r - l + 1] % mod + mod) % mod;
}
void solve() {
	int n;
	std::cin >> n;
	std::vector<ull> a(n+1);
	for(int i = 1; i <= n; i++) {
		std::cin >> a[i];
	}
	P[0] = 1;
	for(int i = 1; i <= n; i++) {
		P[i] = P[i-1] * p;  P[i] %= mod;
		h[i] = h[i-1] * p + a[i]; h[i] %= mod;
	}
	for(int k = 1; k <= n; k++) {
		if( (_hash(1,n-2*k) + _hash(2*k+1,n)) % mod == (2 * _hash(k+1,n-k)) % mod ) {
			std::cout << k <<'\n';
			break;
		}
	}
	return;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int _ = 1;
	//std::cin >> _;
	while(_ --) {
		solve();
	}
	return 0;
}

46

Taki買了 n 種貓糧,第 i 種貓糧的營養值為 a[i]、數量為 b[i]。貓貓的飯量是無窮的,每一天她可以吃任意數量的貓糧,但同一種貓糧她一天只會吃一次。Taki想知道在 k 天內,貓貓可以獲得的最大營養值之和是多少。

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int  p = 1e9 + 7;

struct node{
    int a,b;
};
void solve()
{
    int n;
    std::cin >> n;
    std::vector<node> t(n+1);
    for(int i = 1;  i <= n; i++) {
        std::cin >> t[i].a;
    }
    for(int i = 1;  i <= n; i++) {
        std::cin >> t[i].b;
    }
    std::sort(t.begin()+1,t.end(),[](node A,node B){
        return A.b < B.b;
    });
    int q,k;
    std::vector<ll> sum(n+1),pre(n+2);
    for(int i = 1; i <= n; i++) {
        sum[i] = sum[i-1] + 1ll * t[i].a * t[i].b;
    }
    for(int i = n; i >= 1; i--) {
        pre[i] = pre[i+1] + t[i].a;
    }
    std::cin >> q;
    while(q --) {
        std::cin >> k;
        int l = 1,r = n,x = 0;
        while(l <= r) {
            int mid = (l + r) >> 1;
            if(t[mid].b <= k) {
                l = mid + 1;
                x = mid;
            }
            else r = mid - 1;
        }
        std::cout << sum[x] + 1ll * pre[x+1] * k << '\n';
    }
    
    return;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

多組資料\(t\), 有兩個數字 x, y,她想知道,有多少種方式可以將 x 拆成 y 個正整數的乘積。 例如 x=6, y=2 時,有 6×1=6, 3×2=6, 2×3=6, 1×6=6 這 4 種方法。 由於這個答案可能很大,因此你需要輸出答案對 10^9 + 7 取模後的結果。

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
const int p = 1e9 + 7;

ll fac[1001];
ll ksm(ll a,ll b) {
    ll res = 1;
    while(b) {
        if(b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
void init() {
    fac[0] = 1;
    for(int i = 1; i < 1000; i ++) {
        fac[i] = fac[i - 1] * i % p;
    }
    return;
}
void solve()
{
    int A,B;
    std::cin >> A >> B;
    // A 拆成 B 個 數 乘積 方案數 [12,1] 與 [1,12] 不同  
    std::vector<int> d;
    for(int i = 2; i * i <= A; i ++) {
        if(A % i == 0) {
            int cnt = 0;
            while(1) {
                A /= i;
                cnt ++;
                if(A % i != 0) break;
            }
            d.push_back(cnt);
        }
    }
    if(A > 1) d.push_back(1);
    ll ans = 1;
    for(auto n : d) {
        ll temp = 1;
        for(ll i = B; i <= B - 1 + n; i++) temp = temp * i % p; 
        ans = ans * temp % p * ksm(fac[n],p-2);
        ans %= p;
    }
    std::cout << ans << "\n";
    return;
}
int main()
{
    init();
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

牛客周賽 Round 45

A 簽到題

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)

void solve()
{
    int a,b,c,d,e;
    std::cin >> a >> b >> c >> d >> e;
    std::cout << (( (a + b + c + d + e) > 100 ) ? "YES" : "NO") << '\n';
    return;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

B

需要轉兩個彎的簽到題

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)

void solve()
{
    int n,m;
    std::cin >> n >> m;
    if(m == 1) { std::cout << "YES"; return; }
    if(n % 2 == 0) {
        std::cout <<"YES";
        return;
    }
    else if(m & 1) {
        std::cout <<"YES";
        return;
    }
    std::cout << "NO";
    return;
}
// 3 * 3 
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

C 題目很容易理解 列舉 遞迴 記憶化

    #include <bits/stdc++.h>
    typedef long long ll;
    const int N = 2e6 + 50;
    #define popcount(x) __builtin_popcount(x)
    int f[N];

    bool check(int x) {
        if(f[x] == 1) return true;
        if(f[x] == -1) return false;
        int p = x,cur = 0;
        while(p > 0) {
            cur += p % 10;
            p /= 10;
        }
        if(cur & 1) {
            f[x] = -1;
            return false;
        }
        if(cur == x)  {
            f[x] = 1;
            return true;
        } 
        if(cur < x && check(cur) == true) {
            f[x] = 1;
            return true;
        }
        return false;
    }
    void solve()
    {
        int n,m;
        std::cin >> n;
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            if(check(i))  ans++;
        }
        std::cout << ans << '\n';
        return;
    }
    // 3 * 3 
    int main()
    {
        std::ios::sync_with_stdio(false);
        std::cin.tie(nullptr);
        int _ = 1;
        //std::cin >> _ ;
        while(_ --) {
            solve();
        }
        return 0;
    }

D 思維題 算是 \(two point\) 核心程式碼很短但卻越容易寫錯

#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e6 + 50;
#define popcount(x) __builtin_popcount(x)
int pos[N];
void solve()
{
    int n,k;
    std::cin >> n >> k;
    std::vector<int> a(n+1);
    for(int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }
    int j = 0;
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        if(pos[a[i]]  && i - pos[a[i]] > k) {
            j = std::max(j,pos[a[i]]);
        }
        ans += i - j;
        pos[a[i]] = i;
    }
    std::cout << ans << "\n";
    return;
}
// 3 * 3 
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

注意更換\(j\) 的位置時 不要用上一次出現的位置簡單替換 一定注意 j 是遞增的

最關鍵一行 十八行

E 算是思維題

簡單描述一下題意

樹 問有多少個點到其餘所有點的距離都小於等於 2

一個點 開始 所有與它直接相連的點(u) + 1

所有與u 直接相連的點 加上

看看sum 是否為 n - 1

不會有多算的 因為樹上無環

#include <bits/stdc++.h>
typedef long long ll;
const int N = 3e5 + 50;
#define popcount(x) __builtin_popcount(x)

void solve()
{
    int n;
    std::cin >> n;
    std::vector<int> v[N];
    for(int i = 1,x,y; i < n; i++) {
        std::cin >> x >> y;
        v[x].push_back(y);
        v[y].push_back(x);
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        ll sum = 0;
        for(auto u : v[i]) {
            sum += 1;
            sum += v[u].size() - 1;
        }
        if(sum == n - 1) ans++;
    }
    std::cout << ans << '\n';
    return;
}
// 3 * 3 
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

可笑的是我還分析性質 什麼深度大於5一定沒有解了

滿足性質的答案一定在 某一層或者某兩層 然後列舉這兩層 出題人會不知道重點構圖這兩層嗎 唉

F

純粹的補體力

小橙擁有了一張隨機生成的競賽圖,他喜歡圖上的三元環,即由三個點組成環,請你幫她求出圖上有多少個三元環。

競賽圖是一個有向圖,任意不同的兩點間都恰好有一條單向邊。也就是一共有\(n * (n - 1) / 2\) 條有向邊。

虛擬碼

def rnd():
    ret = seed
    seed = (seed * 7 + 13) mod 1000000007
    return ret mod 2

for i = 1 to n - 1:
    for j = i + 1 to n:
        if rnd() == 0:
            add_edge(i, j) # 從i到j新增一條有向邊
        else:
            add_edge(j, i) # 從j到i新增一條有向邊

思維題

一個符合要求的三元環 其中每個點都符合出度 = 入度 = 1

一個競賽圖有多少個三元環: \(n * (n-1) * (n-2) / 6\) \(C_n^3\)

什麼條件不符合要求

從一個點\(i\) 引出兩條邊 這樣三個點一定不符合要求的三元組 \(C_{deg[i]}^2\) 數量

相減就好

#include <bits/stdc++.h>
typedef long long ll;
const int N = 3e5 + 50;
const int mod = 1e9 + 7;
#define popcount(x) __builtin_popcount(x)
ll n,seed;
int deg[N];
ll rnd() {
    ll ret = seed;
    seed = (seed * 7 + 13) % mod;
    return ret % 2;
}
void solve()
{
    std::cin >> n >> seed;
    if(n <= 2) {std::cout << 0 <<'\n'; return; }
    for(int i = 1; i < n; i++) {
        for(int j = i + 1; j <= n; j++) {
            if(rnd() == 0)  deg[i]++;
            else deg[j]++;
        }
    }
    ll num = 1ll * (n - 1) * 1ll * (n - 2) * 1ll * n / 6; 
    for(int i = 1; i <= n; i++) {
        num -= 1ll * deg[i] * 1ll * (deg[i] - 1) / 2;
    }
    std::cout << num << "\n";
    return;
}
// 3 * 3 
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int _ = 1;
    //std::cin >> _ ;
    while(_ --) {
        solve();
    }
    return 0;
}

競賽圖(tournament graph)是一個有向圖,其中每一對頂點之間恰有一條有向邊。這意味著對於任意兩個不同的頂點 ( u ) 和 ( v ),要麼存在從 ( u ) 指向 ( v ) 的有向邊 ( (u, v) ),要麼存在從 ( v ) 指向 ( u ) 的有向邊 ( (v, u) ),但不會同時存在。

競賽圖具有以下一些重要性質:

  1. Hamilton路徑與Hamilton迴路:

    • Hamilton路徑:一個競賽圖總是存在一個Hamilton路徑,即經過所有頂點且每個頂點恰經過一次的有向路徑。
    • Hamilton迴路:一個競賽圖存在Hamilton迴路的充要條件是圖是強連通的(strongly connected)。這意味著圖中的任意一對頂點之間存在一條有向路徑。
  2. 完全競爭圖

    • 競賽圖的每一對頂點之間都存在唯一的一條有向邊,因此一個n階的競賽圖(即有n個頂點的競賽圖)總共有 ( \frac{n(n-1)}{2} ) 條邊。
  3. 王氏不等式:

    • 在任意一個n階競賽圖中,存在一個頂點,其出度至少為 ( \lceil \frac{n-1}{2} \rceil )。
  4. 主頂點(King):

    • 在一個n階競賽圖中,總是存在一個頂點可以透過至多兩步到達其他所有頂點。這種頂點稱為主頂點(King)。
  5. 反饋弧集(Feedback Arc Set):

    • 競賽圖中的反饋弧集是一個能夠將圖中的所有環破壞的最小有向邊集。對於競賽圖,可以透過某種排序(即線性排序)來找到一個最小的反饋弧集。
  6. 區域性性和全域性性:

    • 競賽圖的許多全域性性質可以透過區域性性質來推導。例如,如果在一個競賽圖的某個子集中存在Hamilton路徑,那麼在整個競賽圖中也可能存在Hamilton路徑。
  7. 強連通性:

    • 如果一個競賽圖是強連通的,則從任意一個頂點出發都可以到達其他任意頂點。這是競賽圖存在Hamilton迴路的一個必要條件。

競賽圖在理論電腦科學、組合最佳化和圖論中有著廣泛的應用。例如,它們可以用於表示錦標賽的賽程安排、對比較複雜系統的優劣關係建模等。 by chatgpt

牛客周賽 41

A: 簽到

#include <iostream>
using namespace std;
int a,b,c;
int main() {
    cin >> a >> b >> c;
    int ans = 0;
    int pot = min(a-b,c-b);
    cout << ((pot < 0) ? ans : pot) << "\n";
    return 0;
}

B: 簽到構造題 將第k個變成第一個 順延前面的即可

注意 k = 1 輸出 -1

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2e6 + 55;
int n,k;
int a[N];
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n  >> k;
    if(n < k || k == 1) {
        cout << -1 << "\n";
        return 0;
    }
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    cout << a[k] << " ";
    for(int i = 1; i < k; i++) {
        cout << a[i] <<" ";
    }
    for(int i = k + 1; i <= n; i++) 
    cout << a[i] << " ";
    cout << "\n";
    //system("pause");
    return 0;
}

C: 迴圈移位 看第多少次是4的倍數

打表4的倍數 發現只需看 最後兩位的十進位制是不是4的倍數 因為可以把大數拆分成100 的倍數 和 小於100 的數的和

需要對 小於10的串特判即可

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 50;
const int M = 2007;
const int P = 1e9 + 7,MOD = 998244353;
const long long INF = 1e18;
typedef long long ll;
int n,m,k;
void solve() {
    string s;
    cin >> s;
    int len = s.length()    ;
    int ans = -1;
    char s1[N];
    if(len == 1) {
        int x = s[0] - '0';
        if(x % 4 == 0) cout << 0 << endl;
        else cout  << -1 << endl;
        return;
    }
    for(int i = 0; i < len; i ++) {
        s1[i] = s[i];
    }
    for(int i = 0; i < len; i ++) {
        int x = (s1[len - 2 + i] - '0') * 10 + (s1[len - 1 + i] - '0');
        if(x % 4 == 0) {
            ans = i;
            break;
        }
        s1[len + i] = s1[i];
    }
    cout << ans << "\n";
    return ;
}
int main() {
    int _ = 1;
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);  
    //cin >> _;
    while(_ --) {
        solve();
    }
    return 0;
} 
// 

牛客周賽 Round 39

F: 01串 A,B 每次選擇某個串 的一個區間 [l,r] 全部 變成1

求 兩個字串 有多少個位置的值都是 1

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
const int P = 1e8;
typedef long long ll;
const int M = 2007;	
int n,m,k;
int a[N],b[N];
struct node{
	int l,r,c1,c2,c;
	// c1: A [l,r] 1 的 個數
	// c2: B [l,r] 1 的 個數
	// c:  Ai & Bi 的串 [l,r] 的 1 的 個數 
	int lz1,lz2;// 懶標記 A  B
}tr[4040404];
void push_up(int u) { // 用兒子更新父親
	tr[u].c = tr[u << 1].c + tr[u << 1 | 1].c;
	tr[u].c1 = tr[u << 1].c1 + tr[u << 1 | 1].c1;
	tr[u].c2 = tr[u << 1].c2 + tr[u << 1 | 1].c2;
	return; 
}
void build(int u,int l,int r) {
    if(l == r) {
	    tr[u] = {l,r,a[l],b[l],a[l] & b[l]};
        return;
	}
	else{
		tr[u] = {l,r,0,0,0};
		int mid = (l + r) >> 1;
		build(u << 1,l,mid);
		build(u << 1 | 1,mid + 1,r);
		push_up(u);
		return;
	}
}
void push_down(int u) { // 父親更新兒子
    if(tr[u].lz1 ) {
		tr[u << 1].c1 = (tr[u << 1].r - tr[u << 1].l + 1);
		tr[u << 1].c = tr[u << 1].c2;
		tr[u << 1 | 1].c1 = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
		tr[u << 1 | 1].c = tr[u << 1 | 1].c2;
		tr[u << 1].lz1 = 1;
		tr[u << 1 | 1].lz1 = 1;
		tr[u].lz1 = 0;
	}
	if(tr[u].lz2) {
		tr[u << 1].c2 = (tr[u << 1].r - tr[u << 1].l + 1);	
		tr[u << 1].c = tr[u << 1].c1;
		tr[u << 1 | 1].c2 = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
		tr[u << 1 | 1].c = tr[u << 1 | 1].c1;
		tr[u << 1].lz2 = 1;
		tr[u << 1 | 1].lz2 = 1;
		tr[u].lz2 = 0;
	}
}
void modify(int u,int l,int r,int op) { // 區間修改
	if(l <= tr[u].l && tr[u].r <= r){
		if(op == 1) {
			tr[u].c1 = (tr[u].r - tr[u].l + 1);
			tr[u].c = tr[u].c2;
			tr[u].lz1 = 1;
		}
		else{
			tr[u].c2 = (tr[u].r - tr[u].l + 1);
			tr[u].c = tr[u].c1;
			tr[u].lz2 = 1;
		}
		return;
	}
	push_down(u);
	int mid = (tr[u].l + tr[u].r) >> 1;
	if(l <= mid) modify(u << 1,l,r,op);
	if(r > mid) modify(u << 1 | 1,l,r,op);
	push_up(u);
}
void solve() {
	cin >> n;
	string A,B;
	cin >> A >> B;
	for(int i = 0;i < n;i ++) {
		a[i + 1] = A[i] - '0';
		b[i + 1] = B[i] - '0';
	}
	build(1,1,n);// u l r
	cin >> m;
	char p;
	for(int i = 0,l,r;i < m;i ++) {
		cin >> p;
		int op = (p - 'A' + 1);
		cin >> l >> r;
		modify(1,l,r,op);
		cout << tr[1].c << endl;
	}
 	return;
}
int main() {
	int _ = 1;
	//cin >> _ ;
	while(_ --) {
		solve();
	}
	return 0;
}

牛客周賽 Round 38

A : 簽到題

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
int n;
int main() {
    cin >> n;
    cout << (10 - (n % 10)) % 10;
    return 0;
}

B : 需要一個知識點: 若 一個數是 9 的 倍數 ,則 各數位數之和 也是 9 的 倍數

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 40;
typedef long long ll;
string s;
ll sum,ans;
int main() {
    cin >> s;
    int len = s.length();
    for(int i = 0; i < len ; i++) {
        sum += (s[i] - '0');
        if(sum % 9 == 0) ans++;
    }
    cout << ans ;
    return 0;
}

C : 構造一個長為\(n\) ,均為小寫字母,恰好有\(k\) 個 迴文子串 的 字串

\(1 \le n \le 10^{5}\)

\(0 \le k \le \frac{n}{2}\)

被樣例騙裡,以為只用 OO類字串不夠,開始計算長度 x 的 "OOOOOO" 的貢獻 其實沒有必要

最後一串shit程式碼 108.33 / 125 pts

其實只要 列舉 k 個 兩個長度的 ,餘下的 用一個長度的 填充即可

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
int n,k,pos;
int main() {
    cin >> n >> k;
    string s = "";
    string a = "abc", b = "def";
    for(int i = 0,j = 0; i < k; i++) {
        s += a[j];
        s += a[j];
        j = (j + 1) % 3;
    }
    int j = 0;
    while(s.size() < n) {
        s += b[j];
        j = (j + 1) % 3;
    }
    cout << s;
    return 0; 
}

D :每次操作在相鄰陣列間插入一個數 ,平滑值: 相鄰兩數差值的絕對值的最大值

讓 平滑值 為 K 最少需要 操作幾次

142.50 / 150

錯誤原因 ; 忽略了當 差值遠小於 K 時 當且僅當需要一次操作 使得平滑值 為 K

我程式碼預設了 小於K時 操作次數不新增

舉例 : 1 3 5 7 12

1/ 13 /3/ 5/ 7/ 即可

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
ll a[N],c[N];
ll n,k,ans;
int main() {
    cin >> n;
    cin >> k;
    for(ll i = 1; i <= n; i++) {
        cin >> a[i];
        c[i] = abs(a[i] - a[i-1]);
    }
    c[1] = 0;
    bool flag  = false;
    for(ll i = 1; i <= n; i++) {
        //cout << c[i] << " ";
        if(c[i] >= k) flag = true;
        ll x = c[i] % k;
        ans += c[i] / k;
        if(c[i] != 0 && x == 0) ans--;
    }
    if(flag == false) ans++;
    cout << ans;
    return 0;
}

E : 長度 為 \(n\) 的陣列 \(a\) , 選擇最多的 數 使得能夠 構成 等比數列 ,公比 \(q\) 為 正整數 輸出 最多的數量

5
3 4 2 1 4

3

最初的設想是 先排序 利用dp ,狀態量 是 \(a[i]\) 作為 等比某一項的 最大 長度

但是我 dp 很爛 ,不好寫

真的沒有想到 能夠 列舉 公比 \(q\)

聽講解 : 公比 q 多× 幾次 就能超 資料範圍 就算 q = 2 ,也僅需 17 次 完全是 常數級的複雜度

注意 q = 1 的 情況;

利用 map 標記 數出現次數 每個 a[i] 列舉 × q 看看 是夠存在 但是 必須連續

還有一處 特別巧妙的地方 :::

pow(q,ans) <= 2e5

很多時候 我們 利用 q = 1 或 2 得 到了一個 ans ,已經比較大了 (假設是10),而且已知 。。

這時我們列舉 公比 10 ,\(10^{ans} > 2e5\) 那我們根本不用 看10了 ,就算 10為公比 的數在陣列中很多 ,但絕對不會多於 \(ans\)

對我們要進行的 ans 更新沒有任何作用 ,大大降低了時間複雜度

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
const int M = 2007;
int n;
int a[N];
map<int,int> mp;
void solve() {
	cin >> n;
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		mp[a[i]]++;
		ans = max(ans,mp[a[i]]);
	}
	for(int q = 2; pow(q,ans) <= 2e5 ; q ++) {
		for(int i = 1; i <= n; i++) {
			int now = a[i],cnt = 0;
			while(now <= 2e5 && mp[now] > 0) {
				cnt++;
				now *= q; 
			}
			ans = max(ans,cnt);
		}
	}
	cout << ans << endl;
}
int main() {
	int _ = 1;
	// cin >> _ ;
	while(_ --) {
		solve();
	}
	return 0;
}

更新:資料有加強 現在這個方法只能過 96.88%

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 50;
typedef long long ll;
const int M = 2007;
int n;
void solve() {
	cin >> n;
	int ans = 0;
	vector<int> a(N + 1),cnt(N + 1);
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		cnt[a[i]]++;
	}
	for(int i = 1; i <= 2e5; i++) {
		ans = max(ans,cnt[i]);
		if(cnt[i] == 0) continue;
		for(int j = i + i; j <= 2e5; j += i) {
			if(cnt[j] == 0) continue;
			int c = 2;
			int now = j;
			int q = j / i;
			while(now <= 2e5 && cnt[now] > 0) {
				ans = max(ans,c);
				now *= q;
				c ++;
			}
		}
	}
	cout << ans << endl; 
}
int main() {
	int _ = 1;
	// cin >> _ ;
	while(_ --) {
		solve();
	}
	return 0;
}

新更改的程式碼時間複雜度還是不會算 ,主要區別在於列舉 q 改為 列舉 a[i] 的 倍數 ,如果有倍數 就可以算出 q 根據連續的原則 等比地去找

**F: ** 進行\(q\) 次詢問,每次問一個區間 \([l,r]\) 是否存在 迴文子串 (長度 嚴格大於 2) (注意子序列 可以不連續 )

當時看到題目時就想到 線段樹,莫隊 樹狀陣列 ,來找 區間有沒有 迴文串 ,但是沒有做過類似的題目 ,不知道 怎麼寫 就放棄了

講解注意: 1 1 2 1 發現 一個區間 只要存在有 不相鄰的 兩個數相等 就一定能滿足條件

la[i] 儲存的是 \(a[i]\) 上次出現的座標 ,其中 \(la[i] != i - 1\)

只要區間內有 \(la[i]\) 且 值 在\([l,r]\) 內 即可 區間 \(la[l,r]\) 最大值 \(\ge l\) 即可

問題回到區間查詢最大值 。。 這樣 線段樹 , RMQ 可以做了

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 50;
typedef long long ll;
const int M = 2007;
int n,q;
int f[M][30];
void solve() {
	cin >> n >> q;
	vector<int> Log(N),a(N),la(N);
	map<int,int> mp;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		if(mp.count(a[i])) {
			la[i] = mp[a[i]];
		}
		mp[a[i]] = i;
	}
	for(int i = n; i ; i--) {
		if(la[i] == i - 1) 
		la[i] = la[la[i]];
	}
	//for(int i = 1; i <= n; i++) {
	//	cout << la[i] << " ";
	//}   cout << endl;
	for(int i = 1; i <= n; i++) f[i][0] = la[i];
	Log[1] = 0, Log[2] = 1;
	for(int i = 3; i <= n; i++) Log[i] = Log[i/2] + 1;
	for(int j = 1; j <= Log[n]; j ++) {
		for(int i = 1; i + (1 << j) - 1 <= n; i++) {
			f[i][j] = max(f[i][j-1],f[i + (1 <<(j-1))][j-1]);
		}
	}
	for(int i = 1; i <= q; i++) {
		int l,r;
		cin >> l >> r;
		int p = Log[r - l + 1];
		int pos = max(f[l][p],f[r - (1 << p) + 1][p]);
		if(pos >= l) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
}
int main() {
	int _ = 1;
	// cin >> _ ;
	while(_ --) {
		solve();
	}
	return 0;
}

這裡用的是 RMQ ST 倍增 求解 區間最大值

什麼是RMQ問題

\(rmq\) 問題指的是 \(range~~maximum/minimum~~query\) (區間最大/最小值詢問),給定一個區間左端點 \(l\) 和右端點 \(r\) ,詢問 \([l,r]\) 中最值,一般樸素的做法是利用for迴圈在 \([l,r]\) 中不斷判斷更新答案,但這樣做的時間複雜度是 \(O(n)\) ,再乘上查詢次數後時間複雜度更是非常高,這對我們來說非常不理想,因此有三種快速解決RMQ問題的演算法

下面以求區間最大值,一共有 \(n\) 個資料, \(k\) 次詢問為例。

ST表(動態規劃+倍增思想)

演算法原理:

由於 \(rmq\) 問題一個可重複貢獻問題,而倍增能夠快速的覆蓋所有元素,所以能夠快速地求解出任意區間的最大值,ST表本身是一個資料結構,一般用二維陣列來儲存。

實現過程下標從 \(1\) 開始):

\((1)\)\(f[i][j]\) 的含義為以 \(i\) 為起始點(左端點),區間長度為 \(2^j\) 的最大值。區間表示就是 \([i,i+2^j-1]\) ,如果將區間一分為而,則得到 \([i,i+2^{j-1}-1]\)\([i+2^{j-1},i+2^j-1]\) ,這兩個子區間的各自的最大值再取一次最大值就是大區間的最大值

\((2)\)\((1)\) 明白了某個區間可以劃分中點,這個區間最大值可以由左右區間得來,那我們用狀態轉移方程的語言去表達就是 \([i,i+2^{j-1}-1]\longrightarrow f[i][j-1]\)\([i+2^{j-1},i+2^j-1]\longrightarrow f[i+2^{j-1}][j-1]\) 整理就可以得到 \(f[i][j]=min(f[i][j-1],f[i+2^{j-1}][j-1])\)

\((3)\) 對於初始化:我們根據定義 \(f[i][0]\) 代表以下標 \(i\) 開頭並有 \(2^0=1\) 個元素的區間最大值,此時不難發現這個區間只有 \(a[i]\) 自身,那麼最值肯定也是其本身,所以 \(f[i][0]=a[i]\) ,另外,我們發現 \(j\)\(2\) 的冪, \(j\) 的取值不應該太大,其極限取值應該是 \(log_2n\) ,以及我們後續查詢也需要將區間長度取對數,為了快速得到 \(log_2x\) 的值,需要對數表陣列 \(log[i]\) ,初始化為 \(log[1]=0,log[2]=1\) ,往後遞推 \(log[i]=log[i/2]+1\)

\((4)\) 迴圈範圍:發現 \(j\)\(j-1\) 得來,所以可以外層迴圈 \(j\)\([1,log[n]]\)\(0\) 的情況已經在初始化得出),內層迴圈 \(i\)\([1,n]\) ,但右端點(即區間結束點)的取值不可以超過 \(n\) ,由 \((1)\) 得到 \(i+2^j-1<=n\)

\((5)\) 查詢:對於一個區間 \([a,b]\) ,其長度對數 \(p=log[b-a+1]\) ,我們可以劃分成兩個區間(注意這裡不能再從中間一分為二了,因為長度不一定是 \(2\) 的冪,奇數長度有可能會決策到額外的數),從左開始為 \([a,a+2^p]\) ,從右開始為 \([b-2^p+1,b]\) ,這分別代表了「從 \(a\) 開始的 \(2^p\) 個數」、「從 \(b\) 結尾的 \(2^p\) 個數」。這兩個區間一定能夠覆蓋 \([a,b]\) (這裡不作證明),故查詢結果應該是 \(max(f[a][p],f[b-2^p][p])\)

最終程式碼:

注意在剛剛的描述中 \(2^j\) 只是為了方便描述,實際編碼應該用 \(1<<j\) 來寫入

G: 給定 長度為\(n\) 的 陣列 \(a\) ,從 \(a\) 中 刪除 一個區間 ,使得剩餘拼接起來的區間的 逆序對數量 不少於 \(k\) , 求刪除區間 方案數

常見的求 逆序對有兩種方法,歸併排序和樹狀陣列。 這道題是明顯的有修改趨勢,要透過樹狀陣列來求逆序對 。

3 1 2 5 1 2 3 5

樹狀陣列求逆序對的原理是: 類似桶排序 放的位置, 將放的數 看已經有多少 比它大的數已經放過了 ,或者倒著放,看有多少值比它小 。。。 針對值域較小的情況,值域較大的話,需要用到離散化。。。

我們來看 少了一個數 ,逆序對發生了什麼變化: 逆序對減少了 左邊比它大的,右邊比它小的

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 50;
typedef long long ll;
const int M = 2007;	
ll n,m,k,cnt;
ll a[N];
vector<ll> tr1(1000001),tr2(1000001);
ll lowbit(ll i) {
	return i & (-i);
}
void add(vector<ll> & tr,ll x,ll k) {
	for(ll i = x; i <= 1e6; i += lowbit(i)) {
		tr[i] += k;
	}
}
ll getsum(vector<ll> & tr,ll x) {
	ll res = 0;
	for(ll i = x; i ; i -= lowbit(i)) {
		res += tr[i];
	}
	return res;
}
void solve() {
	cin >> n >> m; 
	ll sum = 0;
	//vector<int> a(n + 1);
	for(ll i = 1; i <= n; i++) {
		cin >> a[i];	
	}
	for(ll i = n; i ; i --) {
		add(tr2,a[i],1);
		sum += getsum(tr2,a[i] - 1);
	}
	// 當前 sum  為  給定原序列的逆序對數 
	ll ans = (sum >= m);  
	// 維護 雙指標 (滑動視窗)  必需 刪除的區間 
	for(ll i = 1, j = 1; i <= n; i ++) {
		add(tr2,a[i],-1);  // 刪除 a[i]
		sum -= getsum(tr2,a[i] - 1);// 左側的 
		sum -= getsum(tr1,1e6) - getsum(tr1,a[i]); // 右側 
		while(j <= i && sum < m) { // 把 已經刪除的a[j] 加上 
			add(tr1,a[j],1);
			sum += getsum(tr2,a[j] - 1);
			sum += getsum(tr1,1e6) - getsum(tr1,a[j]);
			j ++;
		}
		ans += (i - j + 1);
 	}
	cout << ans << endl;
}
int main() {
	int _ = 1;
	//cin >> _ ;
	while(_ --) {
		solve();
	}
	return 0;
}

相關文章