大炮妙妙屋
快进来,非常好玩
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
完了