Codeforces 1487G. String Counting 【dp+容斥】

22 篇文章 0 订阅
7 篇文章 0 订阅

题意

你需要构造一个长度为 N N N的字符串,每个字符串最多含某个字符 c i c_i ci,问能构造多少个不含奇回文串的字符串。

N ≤ 400 , N 3 < c i ≤ N N\leq 400,\frac{N}{3} < c_i \leq N N400,3N<ciN

分析

考虑 c i c_i ci有下界,所以至多两个字符会超过上界。
没有奇回文,就是说串中 s i s_i si s i − 2 s_{i-2} si2 不相同,考虑如果是所有字符都没有上限的话,就是前两个字符有26种填法,后面都是25种填法。

根据性质,考虑容斥:

答 案 = 总 的 方 案 数 − 至 少 1 个 超 过 上 限 + 至 少 2 个 超 过 上 限 答案= 总的方案数 - 至少1个超过上限 + 至少2个超过上限 =1+2

发现是什么字符其实不这么重要,主要是数量。

定义 f [ i ] [ j ] [ k ] [ l ] [ p ] f[i][j][k][l][p] f[i][j][k][l][p] 表示已经完成了串中的前i个位置,用了j个上限1字符,k个上限2字符,i-2位的状态是l,i-1位的状态是p

最后累计一个后缀和就好了

代码

#include <bits/stdc++.h>

using namespace std;

template <int MOD_> struct modnum {
  static constexpr int MOD = MOD_;
  static_assert(MOD_ > 0, "MOD must be positive");

private :
	
	using ll = long long;

	int v;

	static int minv(int a,int m) {
	  a %= m;
	  assert(a);
	  return a == 1 ? 1 : int(m - ll(minv(m,a)) * ll(m) / a);
	}

public : 

  modnum() : v(0) {}
  modnum(int v_) : v((v_%MOD+MOD)%MOD) {}
  modnum(long long v_) : v(static_cast<int>((v_%MOD+MOD)%MOD)) {}

  explicit operator int() const { return v; }
  friend std::ostream& operator << (std::ostream& out,const modnum& n) { return out << int(n); }
  friend std::istream& operator >> (std::istream& in,modnum& n) { ll v_; in >> v_; n = modnum(v_); return in; }

  bool operator ! () const { return !(*this); }
  bool operator < (const modnum& b) const { return v < b.v; }
  bool operator > (const modnum& b) const { return v > b.v; }
  bool operator == (const modnum& b) const { return v == b.v; }
  bool operator != (const modnum& b) const { return v != b.v; }
  bool operator <= (const modnum& b) const { return v < b.v || v == b.v; }
  bool operator >= (const modnum& b) const { return v > b.v || v == b.v; }
  
  modnum& operator += (const modnum& b) {
  	v += b.v;
  	if(v >= MOD) v -= MOD;
  	return *this;
  }
  modnum& operator -= (const modnum& b) {
  	v -= b.v;
  	if(v < 0) v += MOD;
  	return *this;
  }
  modnum& operator *= (const modnum& b) {
  	v = int(ll(v) * ll(b.v) % MOD);
  	return *this;
  }
  modnum& operator /= (const modnum& b) {
  	return *this *= b.inv();
  }

  modnum inv() const {
  	modnum res;
  	res.v = minv(v , MOD);
  	return res;
  }

  friend modnum operator ++ (modnum& a, int){ modnum r = a; ++a; return r;}
  friend modnum operator -- (modnum& a, int){ modnum r = a; --a; return r;}
  friend modnum operator + (const modnum& a,const modnum& b){ return modnum(a) += b;} 
  friend modnum operator - (const modnum& a,const modnum& b){ return modnum(a) -= b;}
  friend modnum operator * (const modnum& a,const modnum& b){ return modnum(a) *= b;} 
  friend modnum operator / (const modnum& a,const modnum& b){ return modnum(a) /= b;}
};

using Mint = modnum<998244353>;

const int Mod = 998244353;

Mint dp[2][410][410][3][3],sum[410][410]; 
int n,c[410];

signed main() {
  ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
  cin >> n;
  for(int i=1;i<=26;++i) cin >> c[i];
  memset(dp,0,sizeof(dp));
  int op = 0; dp[op][0][0][0][0] = 1;
  for(int i=1;i<=n;++i,op^=1) {
	memset(dp[op^1],0,sizeof(dp[op^1]));
	for(int j=0;j<i;++j) {
  	  for(int k=0;k<i-j;++k) {
  	  	for(int p=0;p<3;p++) {
		  dp[op^1][j+1][k][p][1] += dp[op][j][k][0][p] + dp[op][j][k][2][p];
		  dp[op^1][j][k+1][p][2] += dp[op][j][k][0][p] + dp[op][j][k][1][p];
		  dp[op^1][j][k][p][0] += (dp[op][j][k][1][p] + dp[op][j][k][2][p]) * Mint(24);
		  dp[op^1][j][k][p][0] += dp[op][j][k][0][p] * (i>2 ? Mint(23) : Mint(24));
		  
		}
  	  }
	}
  }
  
  for(int j=0;j<=n;j++) for(int k=0;k<=n;k++) {
    for(int p=0;p<3;p++) for(int l=0;l<3;l++) {
  	  sum[j][k] += dp[op][j][k][p][l];
    }
  }
  
  for(int j=n;j>=0;j--) for(int k=n;k>=0;k--) {
  	sum[j][k] += sum[j+1][k] + sum[j][k+1] - sum[j+1][k+1];
  }
  
  Mint ans = 26; for(int i=1;i<n;i++) {
    if(i == 1) ans = ans * Mint(26);
	else ans = ans * Mint(25);
  }
  for(int i=1;i<=26;i++) ans = ans - sum[c[i]+1][0];
  for(int i=1;i<=26;i++) for(int j=i+1;j<=26;j++) ans = ans + sum[c[i]+1][c[j]+1];
  cout << ans << endl;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值