<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      做題記錄

      CF2008 G. Sakurako's Task

      紀(jì)念第一次場(chǎng)上切的一道 G 題,想了 \(40\) 多分鐘。

      題意

      給定數(shù)組 \(a\),可以進(jìn)行任意次操作:選定 \(i,j\), 可以操作 \(a_i \leftarrow a_i+a_j\)\(a_i \leftarrow a_i-a_j\)。
      問(wèn) 數(shù)組 a 中不存在的第 \(k\) 個(gè)非負(fù)整數(shù)的最大可能為多少。

      思路

      首先發(fā)現(xiàn)這個(gè)第 \(k\) 個(gè)非負(fù)整數(shù)比較奇怪,不太好入手。但是可以發(fā)現(xiàn)盡量把數(shù)往小了堆是更優(yōu)的。
      轉(zhuǎn)變思路從操作入手,發(fā)現(xiàn)如果我們可以獲得一個(gè)很小的數(shù) \(d\),且其他數(shù)都是 \(d\) 的倍數(shù),那么我們可以先把所有數(shù)都變成 \(0\),只剩下一個(gè) \(d\),然后可以將數(shù)組構(gòu)造成 \(0,d,2d,3d \dots (n-1)d\),保證數(shù)都是最小的。

      那么思考如何得到最小的 \(d\),對(duì)于兩個(gè)數(shù) a,b ,我們可以用類似輾轉(zhuǎn)相減的方法得到 \(gcd(a,b)\),同理對(duì)于 \(a_i\),我們可以得到最小的 \(d = gcd(a_1,a_2,\dots a_n)\),于是本題就做完了。

      Artoj P3183. 游戲升級(jí)

      模擬賽的好題。

      題目大意

      t 組詢問(wèn),每次給定 \(a_1,a_2,c,n\),求:

      \[\sum _{x=1} ^{n} {\left [{\left \lfloor \frac{a_1}{x} \right \rfloor - \left \lfloor \frac{a_2}{x} \right \rfloor = c} \right ]} \]

      整除分塊很一眼,然后想法是先對(duì) \(a_1\) 做一遍用哈希記錄,然后做 \(a_2\) 時(shí)添加答案。

      但是 哈希會(huì)超時(shí)。
      然后可以想到先 對(duì)\(a_1,a_2\) 做完,然后對(duì)于一塊答案相同的部分存在數(shù)組里面,然后就可以用雙指針來(lái)做,少了一個(gè) log。復(fù)雜度 \(O(n \sqrt n)\)

      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define mem memset
      int rd()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      int T,n,a1,a2,b1,b2,c;
      
      unordered_map<int,PII> mp;
      
      int calc(int l,int r,int x,int y)
      {
      	if (x > r ||y <l) return 0;
      	if (l <= x && y <= r) return y-x +1;
      	if (x <= l && r <= y) return r-l+1;
      	if (x <= r && l <= x) return r-x+1;
      	if (y >= l && y <= r) return y-l+1;
      }
      
      struct node{int x,l,r;};
      
      int main()
      {
      	cin >>T;
      	vector<node>p1,p2;
      	while (T--)
      	{
      		//mp.clear();
      		p1.clear(),p2.clear();
      		cin >> a1 >> b1 >> a2 >> b2 >>n;
      		if (a1 < a2) swap(a1,a2),swap(b1,b2);
      		c= b2-b1;
      		int ans = 0;
      		
      		for (int l = 1,r;l <= n;l=r+1)
      		{
      			if (l > a2)
      			{
      				p2.push_back({0,l,n});
      				break;
      			}
      			r = (a2/(a2/l));
      			r = min(r,n);
      			p2.push_back({a2/l,l,r});
      		}
      		for (int l = 1,r;l <= n;l=r+1)
      		{
      			if (l > a1)
      			{
      				p1.push_back({0,l,n});
      				break;
      			}
      			r = (a1/(a1/l));
      			r = min(r,n);
      			p1.push_back({a1/l,l,r});
      		}
      		reverse(p1.begin(),p1.end());
      		reverse(p2.begin(),p2.end());
      		int cnt1 = p1.size(),cnt2 = p2.size();
      		int j = 0;
      		rep(i,0,cnt1-1)
      		{
      			while (j < cnt2-1 && p2[j].x < p1[i].x-c) j++;
      			if (p2[j].x == p1[i].x-c)
      			{
      				ans += calc(p2[j].l,p2[j].r,p1[i].l,p1[i].r);
      			}
      		}
      		cout << ans << endl;
      		
      	}
      	return 0;
      }
      

      Artoj P3184. 難題

      模擬賽的好題,因沒(méi)有特判掛了60 pts。

      解法

      可以發(fā)現(xiàn)構(gòu)成 \(X\) 的方式都是 \(f_{2k-1}x + f_{2k}y = X\),其中 \(f_i\) 為斐波那契的第 i 項(xiàng)。然后發(fā)現(xiàn)這個(gè)式子形如 \(ax + by = c\) , 可以用 exgcd 來(lái)解。

      做到這里以為沒(méi)了,但實(shí)際上有個(gè)地方?jīng)]有考慮到:算法在枚舉斐波那契數(shù)列時(shí),對(duì)于不同的 a,b,算出來(lái)的 x,y 是否可能重復(fù)。

      我們假設(shè)存在:

      \[\left\{\begin{matrix} a_1x +b_1y = c \\a_2x +b_2y = c \end{matrix}\right.\]

      我們發(fā)現(xiàn)解出來(lái) \(y\) 為負(fù)數(shù),不符合條件,因此 \(x,y\) 并不會(huì)重復(fù)。
      但是比較特別的是當(dāng) \(c=0\) 時(shí),\(x=0,y=0\),會(huì)被重復(fù)計(jì)算,因此需要特判 c = 0。
      本題就做完了。

      三元組

      模擬賽遇到的好題,考驗(yàn)對(duì) \(trie\) 樹(shù)的理解。

      一句話題意:給定一個(gè)序列 \(a\)\(n<=5 \times 10^5,a_i <= 2^{30}\),問(wèn) \((i,j,k)\) 滿足 \((a_i\ xor\ a_j) < (a_j \ xor \ a_k)\) 的三元組數(shù)量。

      std 是枚舉 \(i\),但好像枚舉 \(j\) 也可以做。

      做法

      枚舉 \(i\),按位考慮發(fā)現(xiàn)可以從 \(i,k\) 從第幾位開(kāi)始不同考慮。
      因?yàn)榍皫孜幌嗤瑫r(shí)結(jié)果一定是相同的。
      于是我們可以在trie樹(shù)上枚舉 \(a_k\) 從第 \(t\) 位開(kāi)始與 \(a_i\) 不同。
      而 j 可選的條件是 \(a_j\) 的第 \(t\) 位等于 \(a_i\) 的第 \(t\) 位。

      因此對(duì)于每個(gè) \(k\) 貢獻(xiàn)就為 \(pre_{k,t,c} - pre_{i,t,c}\)。
      可以將貢獻(xiàn)拆開(kāi),每個(gè)節(jié)點(diǎn)維護(hù) \(pre_{k,t,c}\) 的和還有 \(k\) 的數(shù)量。
      \(c\)\(a_i\) 的第 \(t\) 位。
      此時(shí)對(duì)于每個(gè) k 都要預(yù)處理出每一位,復(fù)雜度 \(n \log^2 V\),過(guò)不去。
      考慮優(yōu)化,我們發(fā)現(xiàn)對(duì)于每個(gè) \(k\) ,需要查詢的每次都是其所在的那一位,因此只需要保存所在那一位即可。

      具體實(shí)現(xiàn)為對(duì)于 \(trie\) 樹(shù)上每個(gè)節(jié)點(diǎn)保存一個(gè) \(sum\),表示所有 k 的 \(pre_{k,c}\) 的和。
      還有保存一個(gè) \(cnt\),表示 k 的數(shù)量。
      然后枚舉 i,貢獻(xiàn)為 \(sum - cnt * pre[i][c]\)

      點(diǎn)擊查看代碼
      #include <bits/stdc++.h>
      using namespace std;
      
      #define int long long
      #define rep(i,a,b) for (int i = a;i <= b;i++)
      #define ll long long
      
      const int N = 5e5+5;
      
      int n,a[N];
      int son[N*32][2];
      int cnt[N*32][2];
      ll sum[N*32][2];
      int tot;
      ll pre[N][35][2];
      
      void insert(int x,int id)
      {
      	//cout << "insert" << x << endl;
      	int p = 0;
      	for (int i = 30;i >= 0;i--)
      	{
      		int ch = x >> i & 1;
      		if (!son[p][ch]) son[p][ch]  = ++tot;
      		p = son[p][ch]; 
      		for (int c = 0;c < 2;c++)
      		cnt[p][c]++,sum[p][c] += pre[id][i][c];
      	//	cout << p << ' ';
      		//cout << id << ' ' << i << ' ' << ch << endl;
      	}
      	//puts("");
      }
      
      
      int query(int id)
      {
      	int p = 0;
      	ll res = 0;
      	for (int i = 30;i >= 0;i--)
      	{
      		int ch = a[id]>>i&1;
      		//cout << i << endl;
      		if (son[p][!ch])
      		{
      		//	cout << i << endl;
      			int t2 = son[p][!ch],c = ch;
      		//	cout <<t2 << ' ' << ch << endl;
      			res += 1ll*sum[t2][ch] - 1ll*cnt[t2][ch] * pre[id][i][c];
      		}
      		if (son[p][ch])
      		p = son[p][ch];
      		else break;
      	}
      	return res;
      } 
      
      signed main()
      {
      	cin >> n;
      	for (int i = 1;i <= n;i++) scanf("%lld",&a[i]);
      	for (int i = 1;i <= n;i++)
      		for (int j = 30;j >= 0;j--)
      		 pre[i][j][(a[i]>>j&1)] = 1;
      	for (int j = 30;j >= 0;j--)
      		for (int k = 0;k < 2;k++)
      			for (int i = 1;i <= n;i++)
      				pre[i][j][k] += pre[i-1][j][k];
      	// for (int i = 1;i <= n;i++)
      	// {
      		// rep(j,0,3)
      			// rep(k,0,1)
      			// {
      				// cout << i << ' ' << j << ' ' << k << ' ' << pre[i][j][k] << endl;
      			// }
      	// }			
      		ll ans = 0;
      	for (int i = n;i >= 1;i--)
      	{
      	//	cout << query(i)<<endl;
      		ans += query(i);
      		insert(a[i],i);
      	}
      	cout << ans << endl;
      		
      	return 0;
      }
      

      P4284 [SHOI2014] 概率充電器

      P4284 [SHOI2014] 概率充電器

      感覺(jué)是一道非常好的題啊。

      樹(shù)形 DP + 概率期望。

      題意:給定一棵樹(shù),每個(gè)點(diǎn)有概率自己帶電,每條邊也有概率可以傳導(dǎo)電,問(wèn)期望帶電的點(diǎn)的個(gè)數(shù)。

      做法

      首先是一個(gè)非常簡(jiǎn)單經(jīng)典的 \(trick\),看到期望但是值只有 \(1\),于是可以將期望轉(zhuǎn)化成求每個(gè)點(diǎn)被點(diǎn)亮的概率之和。

      于是考慮在樹(shù)上做樹(shù)形 DP。

      首先我們可以設(shè)出 \(g_x\) 表示考慮 \(x\) 及其子樹(shù),使得 \(x\) 帶電的概率。

      這個(gè)轉(zhuǎn)移非常好想:\(\large {g_x = 1 - \sum (1-g_{son} \times p_{x,son})}\)。

      從反面考慮一下就做完了。

      然后需要考慮父親對(duì)當(dāng)前 \(x\) 的影響,設(shè) \(f[x]\) 表示 \(x\) 帶電的概率。

      我們發(fā)現(xiàn)父親能對(duì) \(x\) 產(chǎn)生影響當(dāng)且僅當(dāng)父親不是由 \(x\) 點(diǎn)亮的。

      因此設(shè) \(p_1\) 表示父親不是由 \(x\) 點(diǎn)亮的概率,則有:

      \(\huge p_1 = \frac {(1-f_{fa})}{1-p_{fa,x}*g_x}\)

      那么轉(zhuǎn)移就為:

      \[\huge f_x = 1-(1-g_x) \times (1-p1*p_{x,fa}) \]

      code

      // Problem: P4284 [SHOI2014] 概率充電器
      // Contest: Luogu
      // URL: https://www.luogu.com.cn/problem/P4284
      // Memory Limit: 256 MB
      // Time Limit: 2000 ms
      // Author: Eason
      // Date:2024-09-26 16:32:38
      // 
      // Powered by CP Editor (https://cpeditor.org)
      
      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define adde(a,b) v[a].push_back(b)
      #define rd read
      #define PID pair<int,double>
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 5e5+5;
      const double eps = 1e-8;
      
      int n;
      vector<PID> v[N];
      double p[N];
      
      double f[N];
      double g[N];
      double val[N];
      int fa[N];
      
      void dfs(int x,int fa)
      {
      	if (v[x].size() == 1 && x != 1)
      	{
      		g[x] = p[x];
      		return;
      	}
      	double tp = 1;
      	for (auto [y,pc]:v[x]) 
      	{
      		if (y == fa) continue;
      		dfs(y,x);
      		val[y] = pc;
      		tp *= (1-(pc*g[y]));
      	}
      	tp *= (1-p[x]);
      	g[x] = 1-tp;
      }
      
      void dfs2(int x,int fa,double pc)
      {
      	//cout << x << endl;
      	if (x != 1)
      	{
      		double p1 = f[fa];//p1表示父親的子樹(shù)除x以外的概率。
      		p1 = 1-p1;
      		if (1-g[x]*pc < eps) 
      		{
      			f[x] = 1;
      		}
      		else
      		{
      			p1 /= (1-g[x]*pc);
      			p1 = 1-p1;
      			f[x] = g[x] + (p1 * pc) - g[x] * p1* pc;
      		}
      		
      	}
      	for (auto [y,pc]:v[x]) 
      	{
      		if (y == fa) continue;
      		
      		dfs2(y,x,pc);
      	}
      }
      
      int main()
      {
      	cin >> n;
      	rep(i,1,n-1)
      	{
      		int x= rd(),y = rd(),p = rd();
      		double ds = p/100.0;
      		v[x].push_back({y,ds});
      		v[y].push_back({x,ds});
      	}
      	
      	rep(i,1,n) 
      	{
      		int x = rd();
      		p[i] = x/100.0;
      	}
      	if (n == 1)
      	{
      		cout << p[1] << endl;
      		return 0;
      	}
      	dfs(1,0);
      	f[1] = g[1];
      	dfs2(1,0,0);
      	double ans = 0;
      	rep(i,1,n) 
      	{
      		//cout << i << ' ' << f[i]<< ' '<<g[i] << endl;
      		ans += f[i];
      	}
      	
      	printf("%.6f",ans);
      	return 0;
      }
      

      C. Lazy Narek

      Codeforces Round 972 (Div. 2)

      VP打的,結(jié)果連 C 都做不出來(lái) /qd /qd

      題意:

      給定 \(n\) 個(gè)字符串,任選幾個(gè)按順序拼在一起,設(shè)為字符串 \(S\),令 \(cnt\)\(S\)"narek" 的數(shù)量,問(wèn) \(\max \{S.size - cnt*5 \}\)

      一開(kāi)始想的 \(DP\)\(dp_{i}\) 表示考慮前 \(i\) 個(gè)的答案。然后轉(zhuǎn)移考慮每個(gè)串末尾的可能。但是發(fā)現(xiàn)一個(gè)問(wèn)題就是只能考慮到當(dāng)前這個(gè)和前一個(gè)的貢獻(xiàn)。換句話說(shuō) \(narek\) 跨過(guò)幾個(gè)字符串的情況無(wú)法考慮,遂破防看題解。

      正解

      非常巧妙的 \(DP\) 啊。設(shè) \(dp[i]\) 表示當(dāng)前已經(jīng)匹配了 \(i\) 個(gè)字符,正在匹配 "narek" 中的第 \(i-1\) 個(gè)的答案。

      為什么能這樣設(shè)置呢?因?yàn)槲覀兛梢园l(fā)現(xiàn)答案與當(dāng)前是第幾個(gè)字符串沒(méi)有什么關(guān)系,而且從一個(gè)一個(gè)字符匹配 "narek" 的角度來(lái)思考可以解決跨字符串的問(wèn)題,并且每個(gè)字符串選或不選只關(guān)系到當(dāng)前匹配到第幾個(gè)字符。

      其實(shí)也可以看成是節(jié)省了一維的空間。

      考慮轉(zhuǎn)移。

      枚舉第 \(i\) 個(gè)字符串,枚舉當(dāng)前匹配到了第 \(k\) 位,那么只需要掃一遍字符串,從 \(k\) 位往后循環(huán)匹配,記錄貢獻(xiàn)。

      設(shè)掃完后匹配到了第 \(now\) 位,則轉(zhuǎn)移就為:\(dp[now] = max(dp[now],dp[k]+res)\)

      code

      // Problem: C. Lazy Narek
      // Contest: Codeforces - Codeforces Round 972 (Div. 2)
      // URL: https://codeforces.com/contest/2005/problem/C
      // Memory Limit: 256 MB
      // Time Limit: 2000 ms
      // Author: Eason
      // Date:2024-09-27 10:51:26
      // 
      // Powered by CP Editor (https://cpeditor.org)
      
      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define adde(a,b) v[a].push_back(b)
      #define rd read
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 2e3+5;
      
      int t;
      int n,m;
      int dp[10];
      
      int tmp[10];
      
      string str[N];
      
      string narek = "narek";
      
      int main()
      {
      	cin >> t;
      	while (t--)
      	{
      		cin >> n >> m;
      		rep(i,1,n) cin >> str[i];
      		memset(dp,-INF,sizeof dp);
      		dp[0] = 0;
      		
      		
      		rep(i,1,n)
      		{
      			memcpy(tmp,dp,sizeof tmp);
      			rep(k,0,4)
      			{
      				int now = k,res = 0;
      				rep(j,0,m-1)
      				{
      					int idx = narek.find(str[i][j]);
      					if (idx == -1) continue;
      					if (idx != now) res--;
      					else res++,now = (now+1)%5;
      				}
      				tmp[now] =  max(tmp[now],dp[k] + res);
      			}
      			memcpy(dp,tmp,sizeof dp);
      		}
      		int ans = 0;
      		for (int i = 0;i < 5;i++) ans = max(ans,dp[i]- 2 * i);
      		cout << ans << endl;
      	}
      	return 0;
      }
      

      關(guān)于最后為什么要寫(xiě)

      for (int i = 0;i < 5;i++) ans = max(ans,dp[i]- 2 * i);
      

      而不是輸出 \(dp[0]\) ,這是因?yàn)榈阶詈蟛灰欢ㄊ莿偤闷ヅ渫?,可能多匹配了幾位?/p>

      HACK:

      1
      2 5
      ppppn  
      arekn
      

      如這個(gè)數(shù)據(jù),最后會(huì)匹配成 "narekn",因此 \(dp[1] - 1\times 2\) 才是答案。

      CSP-S 模擬賽 A. 序列

      給點(diǎn)序列 \(a_1,a_2,\cdots ,a_n\),問(wèn)有多少非負(fù)整數(shù)序列 \(x_1,x_2,\cdots,x_n\) 滿足:

      • \(\forall i,0 \le x_i \le a_i\)。
      • 滿足 \(x_1 | x_2 | \cdots | x_n = x_1 + x_2 + \cdots + x_n\)

      \(n \le 16,a_i < 2^{60}\)

      狀壓+數(shù)位 DP 好題。

      條件二可以轉(zhuǎn)化為 \(x\) 兩兩并為 \(0\)。按位考慮,即每一位至多一個(gè) \(1\)。

      考慮從高位開(kāi)始給每一位填數(shù),那么需要判斷是否超過(guò) \(a_i\) ,可以想到用數(shù)位 \(DP\)

      對(duì)于每一個(gè)數(shù)字記錄當(dāng)前是否達(dá)到最大限制,即數(shù)位 DP 中的 \(limit\)

      用一個(gè) \(16\) 位二進(jìn)制記錄即可。

      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define int ll
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define adde(a,b) v[a].push_back(b)
      #define addev(a,b,c) v[a].push_back({b,c});
      #define rd read
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 25,M = 66,mod = 998244353;
      
      int n,a[N];
      
      int num[N][M];
      
      int f[M][(1<<16)+10];
      
      void solve()
      {
      	cin >> n;
      	rep(i,1,n) a[i] = rd();
      	
      	for (int i = 1;i <= n;i++)
      	{
      		for (int j = 1;j <= 60;j++)
      		{
      			num[i][j] = (a[i]>>(j-1))&1;
      		}
      	}
      	for (int j = 0;j < (1<<n);j++) f[0][j] = 1;
      	for (int i = 1;i <= 60;i++)
      	{
      		for (int j = 0;j < (1<<n);j++)
      		{
      			int cur = 0;
      			for (int k = 1;k <= n;k++)
      			{
      				if ((j>>(k-1)&1) && num[k][i]==0) cur += (1<<(k-1));
      			}
      			(f[i][j] += f[i-1][cur])%=mod;
      			for (int k = 1;k <= n;k++) 
      			{
      				int sj = (j>>(k-1))&1;
      				int limit = 1;
      				if (sj == 1) limit = num[k][i];
      				if (limit == 0) continue;
      				int ncur = cur;
      				if (sj) ncur += (1<<k-1);
      				(f[i][j] += f[i-1][ncur])%=mod;
      			}			
      		}
      	}
      	cout << f[60][(1<<n)-1] << endl;
      }
      
      signed main()
      {
      	int t;t = 1;
      	while(t--)
      	{
      		solve();
      	}
      	return 0;
      }
      

      A. 卡牌游戲

      來(lái)源:CSP-S Day 8 模擬賽

      你有兩個(gè)屬性攻擊力 \(A\) 和增量 \(D\),初始為 \(0\)。

      還有一個(gè) \(S\) 表示總傷害,初始為 \(0\)

      你將進(jìn)行 \(n\) 輪游戲,每輪有 \(a_i,b_i,c_i\),每輪開(kāi)始 A += D。

      接下來(lái)選擇一項(xiàng):

      • \(S \ += A+a_i\)
      • \(D \ += b_i\)
      • \(A\ += c_i\)

      問(wèn) \(S\) 的最終最大值。

      考慮計(jì)算每一項(xiàng)操作對(duì)于 \(S\) 的貢獻(xiàn),考慮需要計(jì)算記錄哪些東西。

      操作一可以直接加。

      對(duì)于操作三,若我們知道后面攻擊了 \(cnt\) 次,則我們可以知道該操作對(duì)于 \(S\) 的貢獻(xiàn)為 \(cnt \times c_i\)。

      但是操作二似乎沒(méi)有那么好記錄,發(fā)現(xiàn)增量對(duì)于 \(S\) 的貢獻(xiàn)大概是 \(\sum_j (j-i)*b_i\),其中 \(j\)\(i\) 后面選擇操作 \(1\) 的游戲輪。

      那么我們發(fā)現(xiàn)只需要知道 \(i\) 后面所有的 \(j\) 的和。

      則狀態(tài)設(shè)計(jì)為:\(dp_{i,j,k}\) 表示考慮 \([i,n]\) 輪游戲,選擇 \(j\) 輪游戲進(jìn)行進(jìn)攻,這 \(j\) 輪游戲的輪數(shù)和為 \(k\)。

      則轉(zhuǎn)移就非常簡(jiǎn)單了。

      感覺(jué)是一道 DP 好題,需要仔細(xì)研究狀態(tài)的設(shè)計(jì)。

      const int N = 105;
      
      int n;
      int a[N],b[N],c[N];
      
      
      ll dp[105][105][5005];
      
      void solve()
      {
      	cin >> n;
      	for (int i = 1;i <= n;i++)	a[i] = rd(),b[i] = rd(),c[i] = rd();
      	
      	memset(dp,-INF,sizeof dp);
      	dp[n+1][0][0] = 0;
      	for (int i = n;i >= 1;i--)
      	{
      		for (int j = 1;j <= n-i+1;j++)
      		{
      			for (int k = i*j;k <= n*(n+1)/2;k++)
      			{
      				ll gx1 = -INF,gx2 = -INF,gx3 = -INF;
      				if (j >= 1 && k >= i)
      				gx1 = dp[i+1][j-1][k-i] + a[i];
      				gx2 = dp[i+1][j][k] + 1ll*j * c[i];
      				gx3 = dp[i+1][j][k] + 1ll*(k-j*i)*b[i];
      				dp[i][j][k] = max({gx1,gx2,gx3});
      			}
      		}
      	}
      	ll ans = 0;
      	for (int j = 1;j <= n;j++)
      		for (int k = 0;k <= n*(n+1)/2;k++)
      			ans = max(ans,dp[1][j][k]);
      	cout << ans << endl;
      }
      
      

      時(shí)間復(fù)雜度 \(O(n^4)\),有點(diǎn)小卡。

      A. 中位數(shù)

      來(lái)源:2024 CSP-S 模擬賽 Day 15

      你有一個(gè)序列 \(a_1,a_2,…,a_n\)。

      定義 \(b_{l,r}\) 表示序列 \(\{a_i\}_{l≤i≤r}\) 的中位數(shù)。定義 \(c_{l,r}\) 為序列 \(\{b_{i,j}\}_{l≤i≤j≤r}\) 的中位數(shù)。

      \(\{c_{i,j}\}_{l≤i≤j≤r}\) 的中位數(shù)是多少。

      對(duì)于一個(gè)大小為 \(m\) 的序列,我們定義它的中位數(shù)為第 \(\left \lceil \frac{m}{2}\right \rceil\) 小的數(shù)字。

      一個(gè)經(jīng)典的trick:\(a_i \ge x \rightarrow 1,a_i < x \rightarrow 0\)

      看到中位數(shù),可以想到先二分中位數(shù)為 \(x\)。

      然后我們將大于等于 \(x\) 的數(shù)看成 \(1\),小于 \(x\) 的數(shù)看成 \(0\),得到一個(gè)新序列。

      則通過(guò)我們?cè)谛滦蛄械玫降拇鸢妇涂梢灾来鸢概c \(x\) 的大小關(guān)系。

      在新序列上考慮原問(wèn)題。

      首先 \(b_{l,r}\) 可以通過(guò)一維前綴和直接判斷,當(dāng) \(sum_r-sum_{l-1} > \frac{(r-l+1)}{2}\)\(b_{l,r}\)\(1\)。

      同理,\(c_{l,r}\) 也可以用二維前綴和算出來(lái)。

      然后這題就做完了。

      和這題同個(gè) trick 的經(jīng)典題目:P2824 [HEOI2016/TJOI2016] 排序

      題意是區(qū)間排序操作,在操作最后單點(diǎn)查詢。

      做法是二分答案,然后 \(a_i \ge x \rightarrow 1,a_i < x \rightarrow 0\)。
      那么區(qū)間排序就可以用區(qū)間賦值 \(0/1\) 來(lái)完成。

      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define adde(a,b) v[a].push_back(b)
      #define addev(a,b,c) v[a].push_back({b,c});
      #define rd read
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 1e5+5;
      
      int n,m,p[N];
      
      struct node
      {
      	int op,l,r;
      }Q[N];
      int idx;
      
      struct node2
      {
      	int sum,tag=-1;
      }tr[N<<2];
      
      void stf(int k,int l,int r,int c)
      {
      	tr[k].sum = (r-l+1) * c;
      	tr[k].tag = c; 
      }
      
      void psd(int k,int l,int r)
      {
      	if (tr[k].tag != -1)
      	{
      		int mid = l + r >> 1;
      		stf(k<<1,l,mid,tr[k].tag);
      		stf(k<<1|1,mid+1,r,tr[k].tag);
      		tr[k].tag = -1;
      	}
      }
      
      void modify(int k,int l,int r,int x,int y,int c)
      {
      	if (x > r || y < l) return;
      	if (x <= l && r <= y)
      	{
      		stf(k,l,r,c);
      		return;
      	}
      	int mid = l + r >> 1;
      	psd(k,l,r);
      	modify(k<<1,l,mid,x,y,c);
      	modify(k<<1|1,mid+1,r,x,y,c);
      	tr[k].sum = tr[k<<1].sum + tr[k<<1|1].sum;
      }
      
      int query(int k,int l,int r,int x,int y)
      {
      	if (x > r || y < l) return 0;
      	if (x <= l && r <= y) return tr[k].sum;
      	int mid = l+ r >> 1;
      	psd(k,l,r);
      	return query(k<<1,l,mid,x,y) + query(k<<1|1,mid + 1,r,x ,y);
      }
      void build(int k,int l,int r,int x)
      {
      	if (l == r) 
      	{
      		tr[k] = {(p[l] >= x),0};
      		return;
      	}
      	int mid =l + r >> 1;
      	build(k<<1,l,mid,x);build(k<<1|1,mid+1,r,x);
      	tr[k].sum = tr[k<<1].sum + tr[k<<1|1].sum;
      	tr[k].tag = -1;
      }
      int check(int x)
      {
      	
      	build(1,1,n,x);
      	
      	for (int i = 1;i <= m;i++)
      	{
      		auto[op,l,r] = Q[i];
      		int cnt = query(1,1,n,l,r);
      	//	cout << cnt << endl;
      		if (cnt == 0) continue;
      		modify(1,1,n,l,r,0);
      		if (op == 1) modify(1,1,n,l,l+cnt-1,1);
      		else modify(1,1,n,r-cnt+1,r,1);
      	}
      	
      	return query(1,1,n,idx,idx);
      }
      
      void solve()
      {
      	cin >> n >> m;
      	for (int i = 1;i <= n;i++) p[i] = rd();
      	rep(i,1,m)
      	{
      		Q[i] = {rd(),rd(),rd()};
      	}
      	idx = rd();
      	int l = 1,r = 1e5;
      	while (l < r)
      	{
      		int mid = l + r+1 >> 1;
      		if (check(mid)) l = mid;
      		else r = mid -1;
      	}
      	//cout << check(6) << endl;
      	cout << l << endl;
      }
      
      int main()
      {
      	int t;t = 1;
      	while(t--)
      	{
      		solve();
      	}
      	return 0;
      }
      

      [ABC282Ex] Min + Sum

      [ABC282Ex] Min + Sum

      一道最小值分治的 trick。

      又稱笛卡爾樹(shù)分治。

      題意:求 (l,r) 對(duì)數(shù)滿足 \(1\le l \le r \le n\)\(\sum_{i=l}^rB_i+\min_{i=l}^rA_i\le S\)

      做法考慮分治,將 \(mid\) 設(shè)為區(qū)間最小值的位置,這一步可以用 \(st\) 表做。

      然后將將原區(qū)間 \([l,r]\) 劃分成 \([l,mid-1],[mid+1,r]\) ,因此我們只需要考慮 \((l,r)\) 跨過(guò) \(mid\) 的貢獻(xiàn)。

      我們發(fā)現(xiàn)由于區(qū)間和的單調(diào)性,在確定 \(l,r\) 其中一個(gè)時(shí),另一個(gè)可以二分求得。

      同時(shí)為了保證復(fù)雜度,我們枚舉左右中長(zhǎng)度較小的區(qū)間,于是本題就做完了。

      復(fù)雜度分析

      題解區(qū)還看到一種更nb的做法,直接用單調(diào)棧算出該值作為最小值的最左和最右,然后直接枚舉左右區(qū)間中更小的區(qū)間,二分計(jì)算。

      這兩種方法的復(fù)雜度都可以用從笛卡爾樹(shù)的角度證明。

      考慮每個(gè)位置 \(i\) 在暴力枚舉中產(chǎn)生了多少貢獻(xiàn)。

      設(shè)當(dāng)前區(qū)間節(jié)點(diǎn)為 \(x\),而位置 \(i\)\(x\) 的子樹(shù) \(y\) 中,且 \(y\)\(x\) 較小的子樹(shù)。

      那么在枚舉完子樹(shù) \(y\) 后,下一次再枚舉到 \(i\) 時(shí),就應(yīng)是 \(x\) 作為子樹(shù)進(jìn)行枚舉。

      而又有 \(siz_x \ge 2 \times siz_y\) ,于是我們發(fā)現(xiàn)每一次枚舉 \(i\) 的時(shí)候,其所在區(qū)間就翻倍。

      也就是說(shuō)每個(gè)位置最多被枚舉 \(O(n \log n)\) 次。

      類似啟發(fā)式合并。

      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define int ll
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define mem memset
      #define rd read
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 2e5+5;
      
      int n,a[N],b[N];
      int sum[N];
      int S;
      int dp[N][25];
      void prework()
      {
      	for (int i = 1;i <= n;i++) dp[i][0] = i;
      	for (int j = 1;j <= 20;j++)
      		for (int i = 1;(i+(1<<j)-1) <= n;i++) 
      		{
      			int t1 = dp[i][j-1],t2 = dp[i+(1<<(j-1))][j-1];
      			if (a[t1] < a[t2]) dp[i][j] = t1;
      			else dp[i][j] = t2;
      		}
      }
      int query(int l,int r)
      {
      	int k = log2(r-l+1);
      	int t1 = dp[l][k],t2 = dp[r-(1<<k)+1][k];
      	return ((a[t1] < a[t2])?t1:t2);
      }
      int ans;
      
      int calcl(int id,int l,int r,int num)
      {
      	l--;
      	while (l < r)
      	{
      		int mid = l + r + 1>> 1;
      		if (sum[mid] - sum[id-1] <= num) l = mid;
      		else r = mid - 1;
      	}
      	return l;
      }
      int calcr(int id,int l,int r,int num)
      {
      	r++;
      	while (l < r)
      	{
      		int mid =l + r>>1;
      		if (sum[id]-sum[mid-1] <= num) r = mid;
      		else l = mid + 1;
      	}
      	return l;
      }
      void solve(int l,int r)
      {
      	if (l > r) return;
      	int mid = query(l,r);
      	if (mid-l < r-mid)
      	{
      		for (int i = l;i <= mid;i++) ans += calcl(i,mid,r,S-a[mid]) - mid+1;
      	}
      	else{
      		for (int i = mid;i <= r;i++) ans += mid - calcr(i,l,mid,S-a[mid]) + 1;
      	}solve(l,mid-1);solve(mid+1,r);
      }
      
      signed main()
      {
      	cin >> n >> S;
      	rep(i,1,n) a[i] =rd();
      	rep(i,1,n) b[i] =rd(),sum[i] = sum[i-1] + b[i];
      	prework();
      	solve(1,n);
      	
      	cout << ans << endl;
      	return 0;
      }
      

      CF1956D Nene and the Mex Operator

      *2000

      賊有趣的 \(DP\) 加構(gòu)造。

      題意

      給定長(zhǎng)度為 \(n\) 的序列 \(a_i\),滿足 \(1\leq n\leq 18,0\leq a_i\leq 10^7\)。定義一次操作為將 \([l,r]\) 區(qū)間賦值為 \(a_l,\ldots,a_r\)\(\mathrm{mex}\) 值,求在 \(5\times 10^5\) 次操作之內(nèi)序列和的最大值,并給出操作序列。

      分析

      題目要求求出最大值并給出構(gòu)造方案。

      我們先思考題目給出的操作有什么性質(zhì)。

      由于題目給出的限制 \(cnt<=5 \times 10^5\) 非常大,所以我們大膽嘗試。

      通過(guò)手玩樣例,我們猜測(cè)對(duì)于任意區(qū)間 \([l,r]\) 我們都可以將其中每個(gè)數(shù)變成 \(r-l+1\) ,即區(qū)間長(zhǎng)度。

      等下再證明這玩意,先看看知道了這條性質(zhì)怎么求出最大值。

      考慮 \(DP\),設(shè) \(f_{i}\) 表示前 \(i\) 個(gè)能得到的最大值,直接枚舉 \(j\) 轉(zhuǎn)移:

      \[f_i = \max \{ f[j-1]+(i-j+1)*(i-j+1)\} \]

      過(guò)程中記錄一下上次的轉(zhuǎn)移點(diǎn),就可以知道操作了哪些區(qū)間。

      再考慮剛才的操作,我們假設(shè)區(qū)間長(zhǎng)度 \(len = 5\)

      而最后的狀態(tài)是

      \(5 \ 5 \ 5 \ 5 \ 5\)

      想要達(dá)成這個(gè)狀態(tài),必不可少的是

      \(4 \ 3 \ 2 \ 1 \ 0\)

      設(shè) \(func(l,r)\) 表示對(duì) \([l,r]\) 進(jìn)行一次操作。

      設(shè) \(g_i\) 表示在原區(qū)間形成 \(i,i-1,i-2,\dots 0\) 的所需操作集。

      我們發(fā)現(xiàn) \(g_4 = g_3 + func(1,5) + g_3\)

      于是我們可以得出 \(g_i = g_{i-1}+func(l,r)+g_{i-1}\)

      而操作數(shù)剛好為 \(f_i = 2^{i}-1\)

      \(2^{18} + 18 \le 5 \times 10 ^ 5\) 穩(wěn)穩(wěn)通過(guò)!

      2028E - Alice's Adventures in the Rabbit Hole

      題意

      給定一棵樹(shù),皇后想處死愛(ài)麗絲,而愛(ài)麗絲想逃出去。愛(ài)麗絲若在葉子節(jié)點(diǎn)則被處死,在根節(jié)點(diǎn)則逃出。

      每次操作都有 \(\frac{1}{2}\) 的概率由皇后或愛(ài)麗絲來(lái)操作一次操作可以將愛(ài)麗絲移動(dòng)到相鄰的節(jié)點(diǎn)。

      問(wèn)愛(ài)麗絲起點(diǎn)在 \(1,2,\dots , n\) 時(shí),愛(ài)麗絲逃出的概率。

      做法

      先考慮一條鏈的情況:

      \(1,2,3,\dots,d,\dots,n\)

      假設(shè)起點(diǎn)在 \(i\) 的答案為 \(f_i\)。

      則有

      \[\huge{f_i = \frac{1}{2} \times f_{i-1} + \frac{1}{2} \times f_{i+1}} \]

      \[\huge f_1 = 1,f_n = 0 \]

      \[\huge{2f_i = f_{i-1} + f_{i+1}} \]

      \[\huge{f_i - f_{i-1} = f_{i+1} - f_i = d} \]

      \(f\) 是一個(gè)等差數(shù)列,所以:

      \[\huge f_n = f_1 + (n-1) \times d \]

      \[\huge d = -\frac{1}{n-1} \]

      通項(xiàng)為:

      \[\huge f_i = 1-\frac{i-1}{n-1} \]

      我們?cè)跇?shù)上做一個(gè)短鏈剖分。

      解釋一下,就是把樹(shù)鏈剖分的重兒子變成到最近葉子的距離最小的兒子。

      對(duì)于每條短鏈,我們需要額外算上鏈頂?shù)母赣H。

      然后就可以將其當(dāng)做鏈上的情況了。

      假設(shè)當(dāng)前在 \(i\),\(p\)\(i\) 跑到鏈的父親的概率,則 \(1-p\)\(i\) 跑到葉子的概率。

      因?yàn)楫?dāng)愛(ài)麗絲跑到當(dāng)前鏈的父親上時(shí),此時(shí)已經(jīng)換了一條鏈了,所以需要分開(kāi)考慮。

      則有:

      \[\huge f_i = p \times f_{fa[top]} + (1-p) \times 0 \]

      \(p\) 可以用上面的公式算。

      于是做完了。

      #include<bits/stdc++.h>
      using namespace std;
      #define ll long long
      #define INF 0x3f3f3f3f
      #define re register
      #define int ll
      #define PII pair<int,int>
      #define rep(k,a,b) for (int k = a;k <= b;k++)
      #define adde(a,b) v[a].push_back(b)
      #define addev(a,b,c) v[a].push_back({b,c});
      #define rd read
      int read()
      {
      	int f=1,k=0;char c = getchar();
      	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
      	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
      	return k*f;
      }
      
      const int N = 2e5+5,mod = 998244353;
      
      int n;
      
      int siz[N],top[N],son[N];
      int dep[N];
      int dis[N];
      int fat[N];
      int inv[N];
      vector<int> v[N];
      
      int ksm(int a,int b)
      {
      	int res= 1;
      	while (b)
      	{
      		if (b & 1) res = 1ll*res * a%mod;
      		a = 1ll * a * a %mod;
      		b >>= 1;
      	}
      	return res;
      }
      
      void dfs1(int x,int fa)
      {
      	fat[x] = fa;
      	dep[x] = dep[fa] + 1;
      	for (auto y : v[x])
      	{
      		if (y == fa) continue;
      		dfs1(y,x);
      		if (!son[x] || dis[son[x]] > dis[y]) son[x] = y;
      	}
      	if (son[x])
      	dis[x] = dis[son[x]] + 1;
      }
      int f[N];
      void dfs2(int x,int tp)
      {
      	top[x] = tp;
      	int len = dis[tp]+1 + (tp!=1);
      	int d = dep[x] - dep[tp]+2-(tp==1);d = len-d+1;
      	f[x] = (d-1)*(ksm(len-1,mod-2))%mod*f[fat[tp]]%mod;
      	if (x==1)f[x]=1;
      	if (son[x]) dfs2(son[x],tp);
      	for (auto y : v[x])
      	{
      		if (y == fat[x] || y == son[x]) continue;
      		dfs2(y,y);
      	}
      }
      
      void solve()
      {
      	cin >> n;
      	rep(i,1,n-1) 
      	{
      		int x= rd(),y = rd();
      		v[x].push_back(y);
      		v[y].push_back(x);
      	}
      	dfs1(1,1);
      	dfs2(1,1);
      	rep(i,1,n) cout << f[i] << ' ';
      	puts("");
      	rep(i,0,n) v[i].clear(),f[i] = top[i] = siz[i] = dep[i] = dis[i] = fat[i] = son[i] = 0;
      }
      
      signed main()
      {
      	
      	rep(i,1,2e5) inv[i] = ksm(i,mod-2);
      	int t;t = rd();
      	while(t--)
      	{
      		solve();
      	}
      	return 0;
      }
      

      A. ??

      來(lái)源:2024 NOIP 模擬賽 Day 12

      抽象題目加抽象解法。

      笛卡爾樹(shù) + 樹(shù)形 dp

      題意

      \(n\) 個(gè)人站成一行,能力值分別為 \(a_1, a_2, \dots, a_n\)。

      會(huì)進(jìn)行 \(n - 1\) 輪比賽。每次比賽,裁判會(huì)選出兩個(gè)序列中相鄰的選手,其中能力值更高的選手將獲得勝利,而另一位選手會(huì)被淘汰。當(dāng)兩個(gè)人能力相同的時(shí)候,你可以任意選擇勝者。敗者會(huì)離開(kāi)隊(duì)伍,而勝者的能力值會(huì)增加 \(1\),并且剩下的人合并成一行,繼續(xù)進(jìn)行比賽。

      你可以任意選擇比賽順序和相同情況下的勝者,問(wèn)最后哪些選手可能成為最后的贏家。從小到大輸出這些選手的編號(hào)。

      做法

      我們對(duì)于原序列建笛卡爾樹(shù),首先最大值是一定可以贏的。

      然后我們觀察到每個(gè)節(jié)點(diǎn)可以將其子樹(shù)所有點(diǎn)吃掉。

      設(shè)選手 \(i\) 能否贏為 \(ans_i\)。

      于是我們可以列出 dp:\(f_i\) 表示我們將子樹(shù) \(i\) 刪掉,取代為一個(gè)權(quán)值為 \(f_i\) 的數(shù)且 \(ans_i=1\),滿足 \(f_i\) 最小。

      可以得到:

      • \(f_i \ge a_{fa}\)

      • \(f_i \ge f_{fa} - (siz_{fa} - siz_i)\)

      于是 \(f_i\) 為兩者取個(gè) \(max\)。

      從上往下做就做完了。

      day4 C 互不相同

      給定 \(a_i\),定義一次操作為選擇一段子區(qū)間加上任意整數(shù) \(x\),問(wèn)至少幾次操作使得全局互不相同。

      假設(shè)做了 k 次區(qū)間加法 產(chǎn)生 \(2k\) 個(gè)端點(diǎn)

      可以發(fā)現(xiàn)端點(diǎn)之間每個(gè)區(qū)間內(nèi)部都是互不相同的。

      那么原題可以等價(jià)為我們選擇一個(gè)左端點(diǎn)每次貪心的選直到有相同的就新開(kāi)一個(gè)區(qū)間。

      此時(shí)需要操作的次數(shù) = (區(qū)間數(shù)+1)/2

      因?yàn)橐淮尾僮鲿?huì)產(chǎn)生兩個(gè)端點(diǎn)。

      此時(shí)還有一個(gè)問(wèn)題是左右兩端的貢獻(xiàn)計(jì)算,一種方法是枚舉左端點(diǎn)找出對(duì)應(yīng)右端點(diǎn),

      另一種方法是強(qiáng)行將原序列拼成一個(gè)環(huán),即將原數(shù)組復(fù)制一遍,往后倍增跳滿足 r-l+1<=n

      便能自然地算出左右端貢獻(xiàn)了。

      https://www.luogu.com.cn/problem/CF1237F

      CF1237F Balanced Domino Placements

      題目描述

      給定一個(gè) \(h\)\(w\) 列的方格棋盤(pán),上面已經(jīng)放置了一些多米諾骨牌。每個(gè)多米諾骨牌恰好覆蓋棋盤(pán)上相鄰的兩個(gè)格子,每個(gè)格子最多只能被一個(gè)多米諾骨牌覆蓋。

      我們稱一個(gè)多米諾骨牌的放置方式為“完全平衡”,如果沒(méi)有任何一行或一列中存在被兩個(gè)不同多米諾骨牌覆蓋的兩個(gè)格子。換句話說(shuō),每一行和每一列要么沒(méi)有被覆蓋的格子,要么只有一個(gè)被覆蓋的格子,要么有兩個(gè)被覆蓋的格子且這兩個(gè)格子屬于同一個(gè)多米諾骨牌。

      現(xiàn)在給定一個(gè)已經(jīng)完全平衡的多米諾骨牌放置方式,問(wèn)有多少種方法可以在此基礎(chǔ)上再放置零個(gè)或多個(gè)多米諾骨牌,且仍然保持完全平衡。請(qǐng)輸出方案數(shù)對(duì) \(998\,244\,353\) 取模的結(jié)果。

      當(dāng)前的網(wǎng)格中有部分行部分列被叉掉,問(wèn)還能放骨牌的方案數(shù)。

      直接思考,先放豎著的再放橫著的,發(fā)現(xiàn)豎著的骨牌會(huì)叉掉兩行一列

      對(duì)于橫著的產(chǎn)生影響,互不獨(dú)立,不方便計(jì)算。

      簡(jiǎn)化問(wèn)題,考慮只有豎著的。

      變成了有若干固定行不可選,一次得選兩行的方案數(shù),dp。\(f_{i,j}\) 表示前 \(i\) 行選 \(j\) 次的方案數(shù)。

      當(dāng)然此時(shí)這些選出來(lái)的骨牌的列是錯(cuò)開(kāi)的而且不固定。

      算方案數(shù)我們只需要在剩下沒(méi)有被叉的列中選出 \(j\) 列即可

      因此最終為 \(f_{n,j} \times A_{cnth}^{j}\)

      同理我們也可以計(jì)算出 \(g_{m,i}\) 表示只考慮橫著的。

      此時(shí)似乎橫豎會(huì)互相影響,我們這樣想:

      不具體考慮每一個(gè)骨牌的具體位置,而是考慮使他們錯(cuò)開(kāi),互不影響。

      比如說(shuō)假設(shè)有 \(i\) 個(gè)豎著的,\(j\) 個(gè)橫著的。\(cnt_x \ cnt_y\) 分別是沒(méi)有被叉的行和列的數(shù)量

      \(f_{n,i}\) 算出了 \(n\) 行選出 \(i\) 張骨牌的方案數(shù),

      考慮這 \(i\) 張骨牌能放在哪 \(i\) 列里?

      可以在 \(cnt_x - 2\times j\) 個(gè)列中選擇,因?yàn)橐粡垯M著的會(huì)叉掉兩列。

      乘法原理乘上 \(A_{cntx-2\times j}^{i}\)

      此時(shí)還有一點(diǎn)問(wèn)題。

      橫著的同時(shí)會(huì)叉掉一行,因此橫著的也只能在 \(cnt_y - 2\times i\) 行中選擇

      再乘上 \(g_{m,j} \times A_{cnt_y-2\times i}^{j}\)

      得到最終答案。

      C. 異或可達(dá)

      模擬賽 day5 C

      題意

      給定 \(m\) 條邊和常量 \(k\)\(q\) 個(gè)詢問(wèn),給定\(d\)

      問(wèn) \((x,y)\) 數(shù)量

      滿足 \(x < y\)\(x,y\) 聯(lián)通且每條邊權(quán) \(c_j \ xor \ d < k\)。

      做法

      要維護(hù)無(wú)向圖連通性。

      想到并查集。

      查詢并不簡(jiǎn)單,考慮將查詢離線并放在一起。

      \(c_j \ xor \ d < k\) 限制條件提醒我們將問(wèn)題放在 trie 樹(shù)上思考。

      我們考慮在trie樹(shù)上分治。

      假設(shè)當(dāng)前在 \(x\) ,只考慮當(dāng)前位。

      當(dāng) k = 1,d = 1

      那么 $son_{x,0} $ 整棵子樹(shù)下的邊都應(yīng)被選入。

      于是我們暴力將 \(son_{x,0}\) 整棵子樹(shù)加入,并繼續(xù)在 \(son_{x,1}\) 分治。

      加入后我們用可撤銷并查集將操作撤銷(類似于線段樹(shù)分治

      其他情況同理。

      于是,每條邊都只會(huì)被添加 \(\log V\) 次,問(wèn)題在 \(n \log V \log n\) 的時(shí)間復(fù)雜度被解決。

      模擬賽 day6 D. 阻礙

      題意

      帶權(quán)無(wú)向圖,問(wèn)那些邊刪除后使得 \(1\)\(n\) 的最短路 \(dist_{1,n}\)\(1\)。

      這是一道最短路問(wèn)題,考慮先把最短路徑圖建出來(lái)。

      此時(shí)得到一個(gè) DAG。

      DAG 上思考問(wèn)題不太簡(jiǎn)單。

      不妨我們考慮該 DAG 上的以 \(1\) 為根搜索樹(shù)。

      此時(shí)會(huì)發(fā)現(xiàn)一些性質(zhì):

      1. 滿足條件的邊一定不會(huì)在搜索樹(shù)之外

      ? 因?yàn)閯h除后,搜索樹(shù)的最短路依舊是 \(dist_{1,n}\),因此滿足條件的邊在 \(1-n\) 的路徑上

      1. 考慮維護(hù)每條邊刪除后的最短路,搜索樹(shù)外的一條邊 \((u,v)\) 可以更新他們路徑上的所有邊的答案

        貢獻(xiàn)為:\(dist_{1,u} +dist_{v,n} + w(u,v)\)

      于是得到做法,我們將樹(shù)外的邊按照上面的貢獻(xiàn)從小到大排序,每次覆蓋一段路徑。

      可以使用并查集維護(hù),\(f_i\) 表示 \(i\) 上最近的沒(méi)有被覆蓋的點(diǎn)。初始 \(f_i = i\)

      最后查一遍在 \(1-n\) 路徑上所有的邊就做完了。

      kruskal 重構(gòu)樹(shù)

      P11907 [NHSPC 2023] F. 恐怖的黑色魔物

      在一個(gè)三維的網(wǎng)格上,可以移動(dòng)到相鄰的點(diǎn)。

      點(diǎn)權(quán)值 \(d\) 為最近關(guān)鍵點(diǎn)的曼距。

      \(q\) 次查詢問(wèn) \(s \rightarrow t\) 路徑上最小權(quán)值最大。

      做法

      首先 \(d\) 可以用多源bfs 解決。

      最小權(quán)值最大容易想到最大生成樹(shù),點(diǎn)權(quán)可以通過(guò) \(w(u,v) = \min (d_u,d_v)\) 解決。

      或者直接每次選最大權(quán)值的點(diǎn)找已經(jīng)被加入的鄰居去更新生成樹(shù)。

      可以建 kruskal 重構(gòu)樹(shù)解決。

      值得注意的一個(gè) trick 是在建kruskal重構(gòu)樹(shù)時(shí)不需要跑 lca 來(lái)最小權(quán)值。

      我們的做法是發(fā)現(xiàn)合并兩個(gè)聯(lián)通塊直接只需要任意兩個(gè)點(diǎn)連上邊即可,

      因此我們不需要新建點(diǎn),啟發(fā)式合并兩棵樹(shù)的根,邊權(quán)為原點(diǎn)權(quán)。

      與原重構(gòu)樹(shù)性質(zhì)一致。

      樹(shù)高便變?yōu)榱?\(O(\log n)\),只需要兩個(gè)點(diǎn)往上暴力跳即可。

      代碼大概是這樣:

      find(x);find(y);
      if (dep[x] < dep[y])swap(x,y);
      while (dep[x] > dep[y]) mx = min(maxn,fval[x]),x = fa[x];
      while (x != y)
      {
          mx = min({mx,fval[x],fval[y]});
          x = fa[x],y = fa[y];
      }
      
      int find(int x)
      {
      //	cout<< x <<endl;
      
      	if (fa[x] != x) 
      	{
      		int t =  find(fa[x]);
      		dep[x] = dep[fa[x]] + 1; // 維護(hù)深度
      		return t ; 
      	}
      	dep[x] = 1;
      	return fa[x];
      }
      
      posted @ 2024-09-02 00:57  codwarm  閱讀(111)  評(píng)論(2)    收藏  舉報(bào)
      主站蜘蛛池模板: 黑人巨大av无码专区| 亚洲av永久无码精品水牛影视| 推油少妇久久99久久99久久| 亚洲国产性夜夜综合| 草草线在成年免费视频2| 高清自拍亚洲精品二区| 国产午夜精品在人线播放| 欧日韩无套内射变态| 女人香蕉久久毛毛片精品| 日区中文字幕一区二区| 亚洲春色在线视频| 天堂影院一区二区三区四区| 2021国产精品视频网站| 国产福利深夜在线播放| 资源县| 墨江| 亚洲国产精品成人综合色| 久久综合色之久久综合| 天天燥日日燥| 国产精品亚洲二区在线播放| 亚洲色在线v中文字幕| 无码激情亚洲一区| 少妇无套内射中出视频| 九九热在线精品视频观看| 乳源| 成人无码视频| 夜夜爽妓女8888888视频| 国产麻豆精品一区一区三区| 国产综合欧美| 2020国产成人精品视频| 亚洲性日韩精品一区二区三区| 亚洲精品欧美综合二区| 亚洲欧美日韩综合久久久| 蛟河市| 日韩中文字幕一区二区不卡| 国产爆乳无码av在线播放| 亚洲高清偷拍一区二区三区| 婷婷四虎东京热无码群交双飞视频 | 久久er99热精品一区二区| 精品无码人妻一区二区三区| 国产最大成人亚洲精品|