T3 回文
!!!这是一道极好的枚举题目
小 C 有一个长度为 \(n\) 且由小写字母构成的字符串 \(S\)。
小 C 现在可以对 \(S\) 做一些变换,但变换是需要代价的,将字母 \(c_1\) 变成 \(c_2(c_1\ne c_2)\) 的代价为 \(v_{c_2}\),其中 \(v\) 是一个长度为 \(26\) 的数组。(为了方便理解,规定字母 a 对应数字 \(1\),字母 b 对应数字 \(2\),以此类推)
小 C 在做上述变换前可以先选择两个位置并交换两个位置上的字母(该操作最多做一次且代价为 \(0\)),然后再将字母随意变换。
小 C 想要知道将 \(S\) 变成回文串的最小代价,保证 \(2|n\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ll long long
#define dbug(x) (void)(cerr << #x << " = " << x << endl)
ll n, v[30];
string s;
ll minn = INT_MAX;
ll cost[27][27];
ll freq[27][27]; // 添加频率数组记录字符对出现次数
inline ll oside(ll x) {
return n - x + 1;
}
inline ll f(string s){
ll ans = 0;
for (ll i = 1; i <= n / 2; i++) {
if (s[i] != s[oside(i)]){
int c1 = s[i] - 'a' + 1;
int c2 = s[oside(i)] - 'a' + 1;
ll current_cost = min({2 * minn, v[c1], v[c2]});
ans += current_cost;
int minC = min(c1, c2);// 标准化为有序对,确保第一个索引不大于第二个索引
int maxC = max(c1, c2);
freq[minC][maxC]++; // 记录出现次数
}
}
return ans;
}
int cs(int x, int y){
if(x == y) return 0;
return min({2*minn, v[y], v[x]});
}
int main() {
freopen("palindrome.in", "r", stdin);
freopen("palindrome.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (ll i = 1; i <= 26; i++) {
cin >> v[i];
minn = min(minn, v[i]);
for(ll j = 1; j <= 26; j++){
freq[i][j] = 0; // 初始化频率数组
}
}
cin >> s;
s = " " + s;
ll ans, sum, temp;
temp = ans = sum = f(s);
if (sum == 0) {// 如果已经是回文,直接输出0
cout << 0 << endl; return 0;
}
// 26^4枚举交换可能性,ac一对,bd一对,然后ab进行交换
for(ll a = 1; a <= 26; a++){
for(ll b = a+1; b <= 26; b++){
for(ll c = 1; c <= 26; c++){
for(ll d = 1; d <= 26; d++){
int min_ac = min(a, c);// 标准化字符对为有序对
int max_ac = max(a, c);
int min_bd = min(b, d);
int max_bd = max(b, d);
if (freq[min_ac][max_ac] == 0 || freq[min_bd][max_bd] == 0) continue;// 检查字符对是否存在
if (min_ac == min_bd && max_ac == max_bd) {//x:本质上zhiyou 是同一对,比如a .... b ,a要和b交换,这是不行的
if (freq[min_ac][max_ac] == 1) continue;
}
// 计算旧代价和新代价
ll old_cost = cs(a, c) + cs(b, d);
ll new_cost = cs(b, c) + cs(a, d); //如果像 x那样交换,则c,c一对,a,a一对,所以很多测试点过不去
ans = min(ans, temp - old_cost + new_cost);
}
}
}
}
cout << ans << endl;
return 0;
}
T4 CodeForces 1670F Jee, You See?
小 C 喜欢序列,某一天他随手写下了一个长度为 \(n\) 的序列 \(A\),其中 \(\forall 1\le i\le n\),\(A_i \ge 0\)。
可惜小 C 不小心弄丢了这个序列,但是他保存下了序列 \(A\) 的一些特征。
- \(l\le \sum_{i=1}^n A_i\le r\)。
- \(\bigoplus_{i=1}^n A_i=z\)。
其中 \(\bigoplus\) 为二进制下的异或运算符号,\(l,r,z\) 都为常数。
现在小 C 想要知道多少种可能的序列 \(A\) 满足他所给出的特征,由于答案可能很大,你只需要告诉小 C 答案对 \(10^9+7\) 取模后的值。
- 对于 \(20\%\) 的数据,保证 \(r\le 30\)。
- 对于 \(40\%\) 的数据,保证 \(n\le 20\),\(r\le 500\)。
- 对于另 \(20\%\) 的数据,保证 \(n=2\)。
- 对于另 \(20\%\) 的数据,保证 \(n\le 50\)。
- 对于 \(100\%\) 的数据,保证 \(1\le n\le 10^{3}\),\(1\le l\le r\le 10^{18}\),\(1\le z\le 10^{18}\)。
1.40分dp
状态:前i个数,枚举每个数,异或之后的值,总和。答案dp[n][s+x][p^x] += dp[n-1][s][p];
2.满分处理,非常的数位dp。异或的操作一般都是拆位,通过枚举每一位,如果z的这意味是1,则需要从n个数中选择奇数个当前这一位是1的数字。
dfs中当前这一位,如果为1,则枚举选择奇数个数字是几。
那么如何处理是否比r大?传一个变量b。如果比r的当前位小,则前面位都大也可以,比如
r的每一位是11011,前面的位枚举出来是xxx00,如果当前倒数第三位是1,1>r的这一位,成为xx100,则它一定比r大。反之,如果比r这一位小,即使前面都大,则也比r小。如果相等,则原来大还是大,原来小还是小。
int dfs(int pos,int c,int b) { //第pos位, 进位,是否比当前数字大了
if(pos>len) {
if(!c&&!b) return 1;
return 0;
}
if(~dp[pos][c][b]) return dp[pos][c][b];
int ret=0;
for(int i=0; i<=n; i++) { //枚举有几个数当前pos位是1
if((i&1)!=zz[pos]) continue;//与z的第pos位奇偶相同
int now=(i+c)&1; //当前pos位是几(0或者1)
bool bb=(now<num[pos])?0:(now==num[pos])?b:1; //x的pos位是num[pos]
ret=(ret+dfs(pos+1,(i+c)/2ll,bb)*C[n][i]);
}
return dp[pos][c][b]=ret;
}
浙公网安备 33010602011771号