正睿小题
NOIP2022
字符串
回文类
给定一个字符串 a,你每次可以交换 a 的相邻两个字符,问最少进行多少次交换才能使得 a 变为回文串。数据保证一定能够在有限次交换操作后使得 a 变为回文串。保证有解
样例
abcab
1
对于 100%的数据,1≤|a|≤1e6
想到回文串——> 对称性质——>奇数个还是偶数个讨论——>
1. 奇数个去掉中间那个可以变成偶数个。如果是奇数个,那么必然有一个字符是奇数个。把中间的字符替换后就能转化为偶数类。
2. 回文串左右两边是对称的,考虑一边的状态
3. 难点:你需要从交换相邻的两个字符联想到逆序对,可以证明从小到大排是最优策略
证明:在从小到大排的前提下你能想到逆序对的个数就是答案。如果有两个字符是逆序对,那么交换这两个字符,你就能使逆序对的个数减一。考虑两边是对称的。另一半对应的两个字符也必须交换,逆序对的个数可能再减一或者加一。说明这种变换是不劣的。
逆序对用树状数组可以NlogN解决。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n,a[maxn], c[maxn];
char s[maxn];
vector<int> pos[26];
bool vis[maxn];
void add(int x) {
while (x <= n) {
c[x]++;
x += x & -x;
}
}
int query(int x) {
int ret = 0;
while (x) {
ret += c[x];
x -= x & -x;
}
return ret;
}
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; i++){
pos[s[i] - 'a'].push_back(i);
}
if (n & 1) {
for (int i = 0; i < 26; i++) {
if (pos[i].size() % 2) {
int p = pos[i][pos[i].size() / 2];
a[(n + 1) / 2] = p;
vis[p] = 1;
}
}
}
for (int i = 1, j = 1, k = n; i <= n && j <= k; i++) {
if (vis[i]) {
continue;
}//j,k表示双指针,因为是回文。所以一个是从小到大,一个是从大到小。
a[j] = i;//这里的手法类似于火柴排队那道题。具体功能看样例。通过数组下标和数值确认是否为逆序对。
vis[i] = 1;
int p = pos[s[i] - 'a'].back();//放第二个
a[k] = p;
vis[p] = 1;
j++;
k--;
pos[s[i] - 'a'].pop_back();
}
long long ans = 0;
for (int i = n; i;--i) {//这里必须倒序,因为回文的后一部分是从大到小。
add(a[i]);
ans += query(a[i] - 1);
}
printf("%lld\n", ans);
return 0;
}
/*
abcab
a[3]=3,vis[3]=1
a[1]=1,vis[1]=1
a[5]=4,vis[4]=1
a[2]=2,vis[2]=1
a[4]=5,vis[5]=1
add(4),query(3)=0
add(5),query(4)=1
······
*/
DP
特解类
给定n,求有多少个1~n的排列P满足任意Pi % P(I+1) <= 2,Pn mod P1 <= 2,答案对998244353取模
1.当Pi <Pi+1 时,Pi只能是1或者2.1,2只有两个,可以将这一段数切成两段,以得到两个递减的段。
2.设dp[i]表示填入1~i的方案数。转移时枚举j表示(j,i]在同一个段中且j和i不在同一个段中.如果i<n
那么要求i+1 mod j <=2 .dp[i]+=dp[j]
3.最终答案为n*dp[n],因为考虑循环移位。
4.直接枚举i,j是O(n*n)的,但是我们可以发现i,i-1,i+1中至少有一个是j的倍数,因此我们先枚
举j,然后枚举j的所有倍数转移即可,时间复杂度O(nlogn)。
/*
3
123
132
213
231
312
321
当n=5时
dp[4]+=dp[3]
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+100,mod=998244353;
int n,f[N];
signed main()
{
ios::sync_with_stdio(false);
cin>>n;
if(n<=3){
int res=1;
for(int j=1;j<=n;++j)res*=j;
cout<<res;
return 0;
}
for(int i=n;i>=3;--i)
{
f[i]=1;
if(i!=3) for(int j=i+1;j<=n;j+=i-1) f[i]+=f[j];
for(int j=2*i-1;j<=n;j+=i-1) f[i]+=f[j];
for(int j=2*i-2;j<=n;j+=i-1) f[i]+=f[j];
f[i]%=mod;
}
cout<<f[3]*2*n%mod;
return 0;
}