思路:
数论。
- 结论:如果存在A=ad,B=bd,gcd(A,B)=d,那么对于每一个 kB mod A(0<=k<=a-1)都是d的倍数且为[ 0 , a-1 ]的一种排列。
- 证明:kB=kbd,kB mod A = kbd-yad (y=[kB/A]) = (kb-ya)d。
- 反证法,如果存在iB mod A
jB mod A,则易得ad
(i-j)bd,ab互素,则a
(i-j),但由于k的取值范围,i-j<=a-1,显然不成立,故不存在重复倍数。
- kB mod A = kb - [ kb/a ] * a = a ( kb/a - [ kb/a ] ) < a,故范围确定。
在本题中我们可以令D为B,N为A,kD mod N,每a步都不会重复标记,在第a步时 aD mod N=abd mod ad=0,此时向前走一步,继续进行a步标记。
最后结果就是本轮次数(直接算)+轮数(偏移量)。
记得开ll。
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
void work() {
int n, K, D;
cin >> n >> D >> K;
K--;//第一步0不用走
int a = n / __gcd(n, D);
int lun = K / a;
int ci = K % a;
ll ans = lun + D * ci % n;
cout << ans << '\n';
}
signed main() {
io;
int t;
cin >> t;
while (t--) {
work();
}
return 0;
}
E - Make it Palindrome (atcoder.jp)
思路:
典题,算贡献。
先假设每个子串都要改一半,可以算出最大代价,然后考虑如果s[i] == s[j],那么对于所有[l,r]满足l<=i,r>=j且i-l==r-j的区间都会少改一对,然后枚举相同数字,算出区间。
也可以控制区间去枚举相同的数字。
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
vector<int>a[N];
void work() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
int cur;
cin >> cur;
a[cur].pb(i);
}
int res = 0;
for (int i = 1; i <= n; ++i) {
res += (i / 2) * (n - i + 1); //按长度计算修改总次
}
for (int i = 1; i <= n ; ++i) {
int l = 0, r = a[i].size() - 1;
while (l < r) {
//枚举所有相同的数字,在每组相同的之间有(r-l)个相同的
//对于这l->(r-l)组数来说,每一组都有min(a[i][l],n+1-a[i][r])种字串包含这组数可以不被修改
if (a[i][l] < n + 1 - a[i][r]) {
res -= (r - l) * a[i][l];
l++;
} else {
res -= (r - l) * (n + 1 - a[i][r]);
r--;
}
}
//cout << res << "\n";
}
cout << res << '\n';
}
signed main() {
io;
work();
return 0;
}
//直接算贡献
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
//vector<int>a[N];
int a[N], s[N];
void work() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
s[a[i]]++;
}
int res = 0;
int num = n;
int l = 1, r = n;
while (l <= r) {
s[a[l]]--;
s[a[r]]--;
num -= 2;
res += l * (num - s[a[l]]);//对于这里l位置,l->r之间有多少不同的字串,对于每一个不同的都要修改。
res += (n + 1 - r) * (num - s[a[r]]);
if (a[l] != a[r]) {
res += l;
}
l++;
r--;
}
cout << res << '\n';
}
signed main() {
io;
work();
return 0;
}
F - Maximum Diameter (atcoder.jp)
思路:
算贡献+球盒问题。
单次求法的类似题详见牛客练习赛65B。
前置知识:球盒问题。
参考博客:思维拓展:球盒模型_少儿编程乔老师的博客-CSDN博客
这里说两个点:
1. 虚拟球的思想:如果我们提前在每一个盒子里都放一个假球保证他是非空的, 那么可以按照隔板法直接求C( n+m-1 , m-1 ),这样求出来的是n+m同球m异盒无空方案数,此时我们将每种方案中的每个盒子都拿出来一个球,就可能出现空盒了,但是由于取之前是非空,所以可保证不会出现负球盒。
2. 拆分正整数和自然数用dp,对于正整数,dp[i][j]=dp[i-1][j-1](去掉一球一盒令其分离出一个新状态)+ dp[i-j][j](将i-j状态下的每个项都+1);对于自然数:
我!不!会!d!p!啊!啊!啊!
思路(续):
首先由上面那个题我们就可以知道的结论是:
1. 如果n个点连树,只有n-1条边,度一共有2*n-2个。
2. 最长直径是度数>1的点的个数+1(注意两题直径的定义不同)。
我们要求的就是所有树里(度数>1的点的个数+1),考虑把这两部分分开求:
1. 树的个数就是2n-2个度分给n个点,同球不同盒无空问题,插板解决。
2. 单独考虑每一个点的贡献,我们先把一个度给该点,然后让2n-3个度分给n个点,依然同球不同盒,插板解决,这里求的就是对于每一个点有多少种方案可以做贡献,最后*n即可。
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
#define mod 998244353
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
#define inf 0x3f3f3f3f
const int N = 2e6 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
ll fac[N], inv[N];
ll C(int a, int b) {
if (a < b or a < 0 or b < 0)
return 0;
return 1ll * fac[a] * inv[b] % mod * inv[a - b] % mod;
}
void init() {
fac[0] = fac[1] = inv[0] = inv[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
}
for (int i = 2; i < N; ++i) {
inv[i] = 1ll * inv[i] * inv[i - 1] % mod;
}
}
void work() {
int n;
cin >> n;
ll ans = C(2 * n - 3, n - 1) % mod + n * C(2 * n - 4, n - 1) % mod;
ans %= mod;
cout << ans << '\n';
}
signed main() {
io;
int t;
init();
cin >> t;
while (t--) {
work();
}
return 0;
}