Nun Heh Heh Aaaaaaaaaaa
题面
思路
我们将 n u n h e h h e h nunhehheh nunhehheh与 a . . . a... a...分离;
将 n u n h e h h e h nunhehheh nunhehheh看为匹配串,因为 a a a的贡献可以直接用组合数学来求;
设 f ( i , j ) f(i,j) f(i,j)表示前 i i i个字符中出现了 j j j位的匹配字符
转移方程为 f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j − 1 ) f(i,j) = f(i-1,j) + f(i-1,j-1) f(i,j)=f(i−1,j)+f(i−1,j−1)
也就是这一位的匹配字符我们取与不取两种情况;
对于 a a a的个数,假设我们有 k k k个 a a a,那么我们可以取 1 , 2 , . . . , k 1,2,...,k 1,2,...,k个 a a a;
我们都知道组合数取 0 , 1 , 2 , . . . , k 0,1,2,...,k 0,1,2,...,k个的总和为 2 k 2^k 2k;
那么我们只需要取 2 k − C k 0 = 2 k − 1 2^k-C_k^0=2^k-1 2k−Ck0=2k−1即可;
因为我们不能重复计算贡献,因此需要维护一个 l a s t last last;
具体看代码…
Code
二维DP
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int MOD = 998244353;
string sub = "0nunhehheh";
char str[N];
ll f[N][15];
int qpow(int x,int y){
ll base = x,ret = 1;
while(y){
if(y&1){
ret *= base;
ret %= MOD;
}
base *= base;
base %= MOD;
y>>=1;
}
return ret%MOD;
}
int a[N];
void solve(){
memset(f,0,sizeof f);
memset(a,0,sizeof a);
cin >> (str+1);
int n = strlen(str+1);
for(int i=n;i>=1;--i) a[i] = a[i+1] + (str[i] == 'a');
ll ans = 0;
ll last = 0;
for(int i=1;i<=n;++i){
f[i][1] = f[i-1][1] + ('n' == str[i]);
f[i][1] %= MOD;
for(int j=2;j<=9;++j){
f[i][j] = f[i-1][j] +
(sub[j] == str[i])*f[i-1][j-1];
f[i][j] %= MOD;
}
ans += ((f[i][9]-last)%MOD+MOD)%MOD * ((qpow(2,a[i+1])-1+MOD)%MOD);
last = f[i][9];
ans %= MOD;
}
cout << ans << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}
滚动数组优化
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int MOD = 998244353;
string sub = "0nunhehheh";
char str[N];
ll f[15];
int qpow(int x,int y){
ll base = x,ret = 1;
while(y){
if(y&1){
ret *= base;
ret %= MOD;
}
base *= base;
base %= MOD;
y>>=1;
}
return ret%MOD;
}
int a[N];
void solve(){
memset(f,0,sizeof f);
memset(a,0,sizeof a);
cin >> (str+1);
int n = strlen(str+1);
for(int i=n;i>=1;--i) a[i] = a[i+1] + (str[i] == 'a');
ll ans = 0;
ll last = 0;
for(int i=1;i<=n;++i){
f[1] = f[1] + (sub[1] == str[i]);
f[1] %= MOD;
for(int j=9;j>=2;--j){
f[j] = f[j] +
(sub[j] == str[i])*f[j-1];
f[j] %= MOD;
}
ans += ((f[9]-last)%MOD+MOD)%MOD * ((qpow(2,a[i+1])-1+MOD)%MOD);
last = f[9];
ans %= MOD;
}
cout << ans << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}
Jumping Monkey
题面
思路
首先题目给我们的是一棵树,也就是一整个连通块;
显然任何一个点都能到连通块中最大的点;
那么连通块中最大的点对于其他的点贡献为 1 1 1;
现在我们将最大的点删去,分裂成若干个连通块;
可以发现,每个连通块都是上面问题的一个子问题,可以递归的解决;
那么对于每个连通块,过程如下
- 找到最大的点
- 将这个点所在的连通块中除了最大点之外所有的点贡献+1
- 删去这个最大的点
我们发现这个过程并不容易实现;
因为次小的点可以给最小的点一个贡献,第三小的点可以给次小和最小的点一个贡献,依次类推;
那我们按照权值升序排序,反向建树;
那么每个点的答案就是其在新树中的深度了;
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
vector<int> tr[N],G[N];
int n,dep[N],p[N];
struct Node{
int val,x;
}a[N];
int _find(int x){
if(x == p[x]) return x;
return p[x] = _find(p[x]);
}
void clear(){
for(int i=1;i<=n;++i){
tr[i].clear();
G[i].clear();
//这里之所以p[i]写0 是因为p[i]同时兼顾了点i是否访问过的作用
dep[i] = p[i] = 0;
}
}
void dfs(int u,int fa){
dep[u] = dep[fa] + 1;
for(auto v : G[u]){
if(v != fa){
dfs(v,u);
}
}
}
void solve(){
cin >> n;
clear();
for(int i=1,u,v;i<=n-1;++i){
cin >> u >> v;
tr[u].push_back(v);
tr[v].push_back(u);
}
for(int i=1;i<=n;++i){
cin >> a[i].val;
a[i].x = i;
}
sort(a+1,a+1+n,[](Node p,Node q)->bool{
return p.val < q.val;
});
for(int i=1;i<=n;++i){
int u = a[i].x;
p[u] = u;//表示访问过了 同时初始化
for(auto v : tr[u]){
if(p[v]){
int fv = _find(v);
p[fv] = u;
//v已经遍历过了
G[u].push_back(fv);
}
}
}
dfs(a[n].x,0);
for(int i=1;i<=n;++i) cout << dep[i] << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}
Monopoly
思路
很容易想到,我们应该以 n n n个数为一个周期来考虑;
令 s i + n = s i + S s_{i+n}=s_i+S si+n=si+S
当 S = 0 S=0 S=0,我们只能从前缀和中找一个最近的点;
当 S > 0 S>0 S>0,如下图
其中有一点需要特别注意,因为前缀和中可能有多个相同的余数;
显然 s j s_j sj越大,循环的轮次 k k k就越小;
如果都一样大,那么应该取最靠前的;
Code解释
代码中的unordered_map<ll,set<pil,CMP> >um;
是哈希表;
对于每个余数来说,都对应一个set
,用来找位置最靠前,值最大的
s
j
s_j
sj
时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn)
因为lower_bound
只能找≥x
的值,因此我们需要给
x
x
x以及
s
j
s_j
sj取反
Code
#include <iostream>
#include <unordered_map>
#include <set>
#include <utility>
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pil;
const int N = 1e5 + 10;
ll s[N];
struct CMP{
bool operator()(pil a,pil b){
if(a.first == b.first) return a.second < b.second;
return a.first < b.first;
}
};
unordered_map<ll,set<pil,CMP> >um;
map<ll,int> ma;
int n,m;
ll x,r;
void work_0(){
for(int i=1;i<=m;++i){
while(m--){
cin >> x;
if(x == 0) cout << 0 << '\n';
else if(ma.count(x)){
cout << ma[x] << '\n';
}else cout << -1 << '\n';
}
}
}
ll ans;
void solve(){
ma.clear();
um.clear();
cin >> n >> m;
for(int i=1,u;i<=n;++i){
cin >> u;
s[i] = s[i-1] + u;
//取最近的,否则WA
if(!ma.count(s[i])) ma[s[i]] = i;
}
if(s[n] == 0){
work_0();
return;
}
bool negative = false;
if(s[n] < 0){
negative = true;
for(int i=1;i<=n;++i) s[i] = -s[i];
}
for(int i=1;i<=n;++i){
r = ((s[i]%s[n])+s[n])%s[n];
//值大的排前面 如果值相同,那么位置越靠前越好
um[r].insert({-s[i],i});
}
while(m--){
cin >> x;
if(x == 0){
cout << 0 << '\n';
continue;
}
if(negative) x = -x;
r = ((x%s[n])+s[n])%s[n];
if(um.count(r)){
auto ptr = um[r].lower_bound({-x,0});
if(ptr != um[r].end()){
ans = (*ptr).second;
//注意 这里(*ptr).first 是-s(i)
//因此是加
//轮次 = (x-S_j)/s[n]
ans += n*(x + (*ptr).first)/s[n];
cout << ans << '\n';
}else{
cout << -1 << '\n';
}
}else{
cout << -1 << '\n';
}
}
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}