大炮妙妙屋

快进来,非常好玩

Od deski do deski

怎么说呢,确实想到了删的区间互不交,然后就从放置整个区间去想,就假了

考虑修缮区间,设\(dp_{i,j,0/1}\)表示当前区间不合法/合法,在后面一位放置\(j\)种数就合法了

那就相当于有一个区间前闭后开,开的结尾有\(j\)种补全方法,就可以按照\(i + 1\)位新放的数字是\(j\)种还是剩的\(m - j\)种分讨了

然后前\(i\)位最多\(i\)种,所以是\(n^2\)

甲苯先生的字符串

矩阵裸题,遍历\(s_1\)把相邻的标记为不能走,用\(trick\)\(n - 2\)次幂,最后对矩阵求和即可

某位歌姬的故事

有熟人啊

我们发现,如果一个区间被多个限制包含,设最小限制为\(minn\),那么$ > minn$的限制不影响这个区间,所以我们考虑枚举所有限制,然后找出所有最小限制为当前枚举值的区间来填这个数,由此做大炮

定义\(dp_{i,j}\)表示\(i\)个区间填了当前值,最后一个填了的是\(j\),根据当前区间填不填这个值转移即可

更多细节
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
#define N 1005
using namespace std;
int T;
ll n,q,A;
int qj[N << 1],num;
int l[N],r[N],maxx[N];
int lim[N],h[N];
int minn[N];
int len[N],pt[N];
int dp[N][N];
ll qp(ll base,ll ci)
{
	ll res = 1;
	while(ci)
	{
		if(ci & 1) res = res * base % mod;
		base = base * base % mod;
		ci >>= 1;
	}
	return res;
}
int main()
{
	cin >> T;
	while(T--)
	{
		num = 0;
		memset(l,0,sizeof(l));
		memset(r,0,sizeof(r));
		memset(lim,0,sizeof(lim));
		memset(h,0,sizeof(h));
		memset(maxx,0,sizeof(maxx));
		memset(qj,0,sizeof(qj));
		cin >> n >> q >> A;
		for(int i = 1;i <= q;i++)
		{
			cin >> l[i] >> r[i] >> maxx[i];
			qj[++num] = l[i] - 1;qj[++num] = r[i]; 
		}
		qj[++num] = 0;qj[++num] = n;
		sort(qj + 1,qj + num + 1);
		num = unique(qj + 1,qj + num + 1) - qj - 1;
		for(int i = 1;i <= num;i++) lim[i] = A + 1;
		for(int i = 1;i <= q;i++)
		{
			l[i] = lower_bound(qj + 1,qj + num + 1,l[i] - 1) - qj;
			r[i] = lower_bound(qj + 1,qj + num + 1,r[i]) - qj;
			for(int j = l[i];j < r[i];j++) lim[j] = min(lim[j],maxx[i]);
			h[i] = maxx[i];
		}
		int flag = 0;
		for(int i = 1;i <= q;i++)//如果别的区间压制导致某一区间取不到max就无解 
		{
			int mx = 0;
			for(int j = l[i];j < r[i];j++) mx = max(mx,lim[j]);
			if(mx < maxx[i])
			{
				cout << 0 << endl;
				flag = 1;
				break;
			} 
		}
		if(flag) continue;
//		cout << "M" << endl; 
		sort(h + 1,h + q + 1);
		ll ans = 1;
		for(int i = 1;i <= q;i++)
		{
			int pos = i;
			while(pos < q && h[pos + 1] == h[i]) pos++;
			memset(minn,0,sizeof(minn));
			int tmp = 0;
			//把与当前枚举限制相同(lim相同)的区间提取出来 
			for(int j = 1;j < num;j++)
			{
				if(lim[j] == h[i])
				{
					len[++tmp] = qj[j + 1] - qj[j];
					pt[tmp] = j + 1;
				}
			}
			//在原先限制的区间为当前枚举值的区间中,找到最小限制(lim)是当前枚举值的部分
			//由于原先区间离散化了,所以这里要映射到离散化对应的值 
			//这些部分是填枚举值的部分 
			for(int j = 1;j <= q;j++) 
			{
				if(maxx[j] == h[i])
				{
					int po = upper_bound(pt + 1,pt + tmp + 1,r[j]) - pt - 1;
					minn[po] = max(minn[po],l[j] + 1);
				}
			}
			//dp[i][j]: 填了前 i 块,最后一个填了当前枚举值的区间为 j 
			for(int j = 0;j <= tmp;j++) for(int k = 0;k <= tmp;k++) dp[j][k] = 0;
			dp[0][0] = 1;
			for(int r = 1;r <= tmp;r++)
			{
				ll tot = 0;ll sum = qp(h[i] - 1,len[r]);//剩余部分随便填 
				for(int s = 0;s < r;s++)
				{
					if(!dp[r - 1][s]) continue;
					tot = (tot + dp[r - 1][s]) % mod;
					if(minn[r] > pt[s]) continue;//有交集就跳过 
					//不在当前块填,那就是[1,h[i] - 1]随便填,就是sum 
					dp[r][s] = (dp[r][s] + dp[r - 1][s] * sum % mod) % mod;
				}
				//在这里填,至少得有一个,所以减去 sum 
				dp[r][r] = (qp(h[i],len[r]) - sum + mod) * tot % mod;
			}
			ll res = 0;
			for(int j = 0;j <= tmp;j++) res = (res + dp[tmp][j]) % mod;
			ans = (ans * res) % mod;
			i = pos;
		} 
		for(int i = 1;i < num;i++) if(lim[i] == A + 1) ans = ans * qp(A,qj[i + 1] - qj[i]) % mod;
		cout << ans << endl;
	}
	return 0;
}

Permutation Oddness

逆天转化建议去看第二篇题解

潜入行动

\(dp_{x,i,0/1,0/1}\)表示以\(x\)为根的子树内有\(i\)个监听器,\(x\)有没有放,\(x\)有没有被监听

尤其注意的是,后两个状态表示\(v\)之前的合并完了,还没有合并\(v\)的状态

所以在式子中,等号左边的\(dp_{x,...}\)表示已经将\(v\)并入后\(x\)的状态,右边的则没有,分讨的依据也是\(v\)并入后的状态

分讨见第一篇tj,就是根据后两位共\(2 \times 2 = 4\)种情况讨论

游园会

屌题

\(dp\)\(dp\),说白了,就是记录答案的\(dp\)有一维需要用另一个\(dp\)转移,而不是之前的枚举或是位运算

拿这道题来说,状压\(LCS\)的差分数组作为一维\(S\),这一维度转移时需要枚举字符做一遍\(LCS\)\(dp\)得到\(newS\),从而实现答案的汇总与转移

还有一种理解是用\(LCS\)的大炮搭了个自动机去跑,画出来也很直观(图为样例),每一根线都是当前枚举字符下做一遍\(LCS\),每个括号就是一个答案\(dp\)的状态三元组:\(dp_{i,S,0/1/2}\)表示当前在第\(i\)位,\(LCS\)差分状态为\(S\),匹配到\(NOI\)的第\(0\)(没配上)\(/1/2\)位。其中第三维是为了防止出现\(NOI\),来决定当前位可以放什么

Add and Remove

无语题

改删为加,然后发现有个奇妙的东西:在两个元素中间插入\(x\),那么\(x\)的贡献就是两个元素计入答案的次数之和与\(x\)的乘积

然后你发现\(a_1\)\(a_n\)都只会被计入一次

直接一个暴搜,\(dfs(l,r,numl,numr)\),分别记录当前区间的左右端点以及对应的计入次数,枚举新加入的元素将原区间分成两个子区间:\(dfs(l,p,numl,numl + numr) + dfs(p,r,numl + numr,numr)\),递归取min

完了

posted @ 2024-10-16 22:33  why?123  阅读(2)  评论(0编辑  收藏  举报