920D
题意:给定一个长度为 𝑛 的序列 𝑎和一个长度为 𝑚的序列 b,其中 𝑚≥𝑛。
从b中选出n个元素组成序列c,求D=
∑
i
=
1
n
∣
a
i
−
c
i
∣
\sum_{i=1}^{n} | a_{i}-c_{i} |
∑i=1n∣ai−ci∣最大值。
- 思路一
贪心结论:在 a i a_{i} ai和 b i b_{i} bi分别升序情况时,对于每个 a i a_{i} ai,与其差值最大的 b i b_{i} bi只可能出现在 b n − i + 1 b_{n-i+1} bn−i+1和 b m − i + 1 b_{m-i+1} bm−i+1 这两者之间。
代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
void solve(){
int n,m;
cin>>n>>m;
// vector<int> a(n),b(m);
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<m;i++) cin>>b[i];
sort(a,a+n);
sort(b,b+m);
int ans=0;
for(int i=0;i<n;i++){
ans+=max(abs(a[i]-b[n-i-1]),abs(a[i]-b[m-i-1]));
}
cout<<ans<<endl;
}
signed main(){
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
- 思路二
让a序列从大到小,让b序列从小到大。
最简单的去绝对值,分情况 a i a_{i} ai>= c i c_{i} ci和 a i a_{i} ai< b i b_{i} bi两种情况,第k个数是最后一个 a i a_{i} ai>= c i c_{i} ci情况。遍历k
a[1]-c[1]+a[2]-c[2]+…+a[k]-c[k]+c[m-n+k]-a[k+1]+c[m-n+k+1]-a[k+2]+…+c[m]-a[n] 前k个数ai>ci,后面ai<ci。
所有总体就是 sa[k]-sb[k]-(sa[n]-sa[k])+sb[m]-sb[m-n+k]。前缀和
实现代码如下
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N],sa[N],sb[N];
void solve(){
int n,m;
cin>>n>>m;
// vector<int> a(n),b(m);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
sort(a+1,a+1+n,greater<int>());
sort(b+1,b+1+m);
for(int i=1;i<=n;i++) sa[i]=sa[i-1]+a[i];
for(int i=1;i<=m;i++) sb[i]=sb[i-1]+b[i];
int ans=0,sum;
//a[1]-c[1]+a[2]-c[2]+....+a[k]-c[k]+c[m-n+k]-a[k+1]+c[m-n+k+1]-a[k+2]+...+c[m]-a[n] 前k个数ai>ci,后面ai<ci,所有总体就是sa[k]-sb[k]-(sa[n]-sa[k])+sb[m]-sb[m-n+k]
for(int i=0;i<=n;i++){
sum=(sa[i]+sb[m]-sb[m-n+i]);
int t=sa[n]-sa[i];
sum+=(-t-sb[i]);
ans=max(ans,sum);
}
cout<<ans<<endl;
}
signed main(){
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
920E
- 题意:Alice先走,Bob后走。如果横坐标为奇数,只有Alice可能赢(Bob只能追求平局),否则只有Bob可能赢。
- 思路:能赢的人的条件,只能是其走到了两边的左右边界。一个人要往左右走一步,就得消耗上下的两步。
- 代码实现如下
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int h, w, xa, ya, xb, yb;
std::cin >> h >> w >> xa >> ya >> xb >> yb;
if (std::abs(ya - yb) > xb - xa) {
std::cout << "Draw\n";
} else if ((xb - xa) % 2 == 1) {
int step = (xb - xa + 1) / 2;
if (std::abs(ya - yb) <= 1 || (ya < yb ? w - ya : ya - 1) <= step) {
std::cout << "Alice\n";
} else {
std::cout << "Draw\n";
}
} else {
int step = (xb - xa) / 2;
if (ya == yb || (ya < yb ? yb - 1 : w - yb) <= step) {
std::cout << "Bob\n";
} else {
std::cout << "Draw\n";
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
920F
- 题意:给你一个长度为n的数组,q次询问。每次询问给出首项,公差,个数,求这个等差数列乘以序号之和。
- 思路:
f
i
j
f_{ij}
fij表示公差为i,最后一位数下标为j的处理后前缀和,
g
i
j
g_{ij}
gij表示公差为i,最后一位数下标为j的前缀和.
f i j = f i j − i + a j ⋅ j i for j − i > 0 f_{ij} = f_{ij-i} + a_j \cdot \frac{j}{i} \quad \text{for } j - i > 0 fij=fij−i+aj⋅ijfor j−i>0 g i j = g i j − i + a j for j − i > 0 g_{ij} = g_{ij-i} + a_j \quad \text{for } j - i > 0 gij=gij−i+ajfor j−i>0
代码如下:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 100005;
int a[N];
LL f[320][N], g[320][N];
int main() {
int _;
scanf("%d", &_);
while (_--) {
int n, q;
scanf("%d%d", &n, &q);
int wc = sqrt(n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= wc; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = (j - i >= 1 ? f[i][j - i] : 0) + (LL)a[j] * (j / i);
g[i][j] = (j - i >= 1 ? g[i][j - i] : 0) + (LL)a[j];
}
}
while (q--) {
int s, d, k;
scanf("%d%d%d", &s, &d, &k);
LL ans = 0;
if (d > wc) {
for (int i = 1, j = s; i <= k; i++, j += d) {
ans += (LL)a[j] * i;
}
} else {
ans = f[d][s + (k - 1) * d] - (s - d >= 1 ? f[d][s - d] : 0);
ans -= (s / d - 1) * (g[d][s + (k - 1) * d] - (s - d >= 1 ? g[d][s - d] : 0));
}
printf("%lld ", ans);
}
puts("");
}
return 0;
}
954D
-
题意:有n个字符数字,在其中插入n-2个 + + +或者 × \times ×,求表达式求值后的最小值
-
思路一:贪心。
当 n = 2 or n = 3 n = 2 \text{ or } n = 3 n=2 or n=3时,直接求;
当 n > 3 n > 3 n>3时,数字有0时,答案为0;
当没有 0 0 0时,首先枚举那两个位置的数构成一个两位数,求最小时,当后面一个数为1时,前面符号为 × \times ×否则为 + + +。证明如下:
∀ a , b ∈ { x ∣ x ∈ N ∗ , x ∈ [ 1 , 9 ] } , 不妨设 a b < a + b ,则 a b < a + b ⇒ a < 1 + a b ⇒ a − a b < 1 ⇒ a ( b − 1 ) < b ⇒ a = 1 \forall a, b \in \{x \mid x \in \mathbb{N}^*, x \in [1, 9]\},\text{不妨设} ab < a + b,\text{则} \\ab < a + b\ \Rightarrow a< 1+\frac{a}{b} \\\ \Rightarrow\ a - \frac{a}{b} < 1 \\\ \Rightarrow\ a(b - 1) < b \\\ \Rightarrow\ a = 1 ∀a,b∈{x∣x∈N∗,x∈[1,9]},不妨设ab<a+b,则ab<a+b ⇒a<1+ba ⇒ a−ba<1 ⇒ a(b−1)<b ⇒ a=1 -
思路二:DP。
状态表示: f ( i ) f(i) f(i)表示前i个数构成的表达式最小值 ( 集合 a n d 属性) (集合 and 属性) (集合and属性)
状态转移:表达式中最后一个 + + +在第 j j j个数之前。枚举 j j j的位置。即
f ( i ) = min j = 1 i ( f j − 1 + ∏ k = j i a k ) f(i) = \min_{j=1}^{i} \left( f_{j-1} + \prod_{k=j}^{i} a_k \right) f(i)=j=1mini fj−1+k=j∏iak -
代码如下:
代码一:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=25;
int a[N];
int ctoi(char ch){
return ch-'0';
}
void solve(){
int n;
string s;
cin>>n>>s;
if(n==2){//直接输出
int t=stoi(s);
cout<<t<<endl;
return;
}
if(n==3){//分情况讨论,前两个字符为一个数或者后两个字符为一个数,执行+或者*的操作
int mn=1e9;
int t1=stoi(s.substr(0,2)),t2=ctoi(s[2]);
mn=min(mn,min(t1+t2,t1*t2));
t1=ctoi(s[0]),t2=stoi(s.substr(1,2));
mn=min(mn,min(t1+t2,t1*t2));
cout<<mn<<endl;
return;
}
//n大于3时,有0则结果为0
int mn=1e9;
for(int i=0;i<n;i++){
if(s[i]=='0'){
cout<<0<<endl;
return;
}
}
for(int i=0;i<n-1;i++){//唯一一个由两个字符组成的数第一个字符的下标
int cnt=1;
for(int j=0;j<n;j++){//预处理,将每个数都放在a数组中
if(j==i){
int res=ctoi(s[i]);
res=res*10+ctoi(s[i+1]);
j++;
a[cnt++]=res;
}else{
a[cnt++]=ctoi(s[j]);
}
}
int res=a[1];//计算当前的最小值
for(int j=2;j<=n-1;j++){
if(res==1||a[j]==1){
res*=a[j];
}else res+=a[j];
}
mn=min(res,mn);
}
cout<<mn<<endl;
}
signed main(){
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
代码二:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=25;
__int128 a[N],f[N],mul[N][N];
int ctoi(char ch){
return ch-'0';
}
inline __int128 read(){
__int128 x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void print(__int128 x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9)
print(x / 10);
putchar(x % 10 + '0');
}
void solve(){
int n;
__int128 mn=1e9;
string s;
cin>>n>>s;
for(int i=0;i<n-1;i++){//唯一一个由两个字符组成的数第一个字符的下标
int cnt=1;
for(int j=0;j<n;j++){//预处理,将每个数都放在a数组中
if(j==i){
int res=ctoi(s[i]);
res=res*10+ctoi(s[i+1]);
j++;
a[cnt++]=res;
}else{
a[cnt++]=ctoi(s[j]);
}
}
for(int j=1;j<cnt;j++){
__int128 t=1;//20个9相乘会爆long long
for(int k=j;k<cnt;k++){
t*=a[k];
mul[j][k]=t;
}
}
memset(f,0x7f,sizeof f);
f[0]=0;
for(int j=1;j<cnt;j++){//求前j个数组成的表达式最小的值,枚举最后一个+号出现的位置,在第k个数前面
for(int k=1;k<=j;k++){
f[j]=min(f[j],f[k-1]+mul[k][j]);
}
}
mn=min(f[n-1],mn);
}
print(mn);
cout<<endl;
}
signed main(){
int T=1;
cin>>T;
while(T--){
solve();
}
return 0;
}
954E
- 题意:可以让任何一个数加 k k k,求执行最少次操作,使得数组都相等。
- 思路:一共要凑
n
/
2
n/2
n/2对相同的数,不管
n
n
n为奇偶数,即最多只能让一个数置身事外。
两个数 u , v u,v u,v如果相差 k k k的倍数则可以在执行完操作后相等,即 u , v u,v u,v模 k k k的余数相等。所以我们将所有余数相同的数放一组。
当余数相同的个数 n n n为偶数时,易知从小到大两两匹配;当 n n n为奇数时,我们可以只能让下标为奇数位置的数置身事外。(从小见大,当 n = 3 n=3 n=3时,如果第二位的数置身事外明显会使得操作次数大于第一位或者第三位置身事外的操作)前缀和或者模拟实现 - 代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
void solve() {
int n,k;
cin>>n>>k;
map<int,vector<int>> mp;
vector<int> sn(n),sm(n);//不要重复在for循环内开,不然会超时
for(int i=0; i<n; i++) {
int x;
cin>>x;
mp[x%k].push_back(x);
}
int odd=0,ans=0;
for(auto t:mp) {
auto a=t.second;
sort(a.begin(),a.end());
if(a.size()%2==0) {
for(int i=1; i<a.size(); i+=2) {
ans+=(a[i]-a[i-1])/k;
}
} else {
odd++;
// int res=1e18;
// int cn=1,cm=1;
// sn.clear(),sm.clear();
// for(int i=1; i<a.size(); i+=2) { //前cn对匹配的前缀和
// sn[cn]=sn[cn-1]+(a[i]-a[i-1])/k;
// cn++;
// }
// for(int i=a.size()-1; i-1>=0; i-=2) { //后sm对匹配的后缀和
// sm[cm]=sm[cm-1]+(a[i]-a[i-1])/k;
// cm++;
// }
// for(int i=0; i<a.size(); i+=2) {
// int t=i/2,tt=a.size()/2;//t为前缀和的下标,tt为总共下标。
// int re=sn[t]+sm[tt-t];
// res=min(res,re);
// }
int sum = 0;
for (int i = 1; i < a.size(); i += 2) {
sum += (a[i + 1] - a[i]) / k;
}
int res = sum;
for (int i = 0; i + 1 < a.size(); i += 2) {
sum += (a[i + 1] - a[i]) / k;
sum -= (a[i + 2] - a[i + 1]) / k;
res = min(res, sum);
}
ans += res;
}
}
if(odd>1) ans=-1;
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T=1;
cin>>T;
while(T--) {
solve();
}
return 0;
}
954F
-
题意:一个无向连通图,求删除一条边后,各个连通块内可达点对数之和最小。
-
思路:tarjan算法。
可知删除桥会使得顶点对的数目最小,所以先缩点形成一颗树,然后再枚举删除树上哪条边会使得顶点对最小。预处理每颗子树的点数, sz[u] \text{sz[u]} sz[u]表示以u为根的子树中所有的点数。 -
代码
955B
-
题意:求执行k次操作后x的值是多少
-
思路:模拟加数学。
当 x ≠ 1 x \neq 1 x=1时,模拟需要多少次操作可以使得x能被y整除。
当 x = 1 x = 1 x=1时,每次需要y-1次操作使得x被y整除后重新变为1,所以执行k次操作后 x + = k % ( y − 1 ) x \;+=\; k \% \left(y - 1\right) x+=k%(y−1) -
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 5;
int a[maxn];
void solve() {
int x, y, k;
cin >> x >> y >> k;
while (k && x != 1) {
int temp = min(y - x % y, k);
k -= temp;
x += temp;
while (x % y == 0) x /= y;
}
// 1 + (y - 1) ==> y
k %= (y - 1);
x += k;
cout << x << endl;
}
int main() {
ios::sync_with_stdio(0);
int T;
cin >> T;
while (T--) solve();
}
955D
- 题意:在 n × m n \times m n×m的地图中,有两种类型的山,可以执行任意次操作让 k × k k \times k k×k的正方形中所有的山同时加上一个数(可正可负),最后判断两种山的高度总和是否能相等。
- 思路:首先看
k
×
k
k \times k
k×k的正方形能让两种山的差值怎么变化,即正方形内两种山的数量差值
×
K
(
K
≠
0
)
\times K (K \neq 0)
×K(K=0),枚举所有正方形差值,找到的最大公约数。
如果初始的差值是最大公约数的倍数且最大公约数不为零或者初始差值为零,说明可以使得这两种山的高度之和相等,否则不可以。
g c d ( a , b ) = g c d ( b , a ) = g c d ( b , a m o d b ) = g c d ( ± a , ± b ) = gcd ( a , b − k ⋅ a ) gcd(a, b) =gcd(b, a) = gcd(b, a \mod b) = gcd(\pm a, \pm b) = \text{gcd}(a, b - k \cdot a)\quad \text{} \quad gcd(a,b)=gcd(b,a)=gcd(b,amodb)=gcd(±a,±b)=gcd(a,b−k⋅a)
a m o d b = a − k ⋅ b a \mod b = a - k \cdot b amodb=a−k⋅b - 代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define x first
#define y second
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vint;
const int N=2e5+10;
int a[510][510],pre[510][510],b[510][510];
void solve(){
int n,m,k;
cin>>n>>m>>k;
int sum=0;//初始时01数值的差值
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
char ch;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>ch;
if(ch=='1'){//1的时候为1,0的时候为-1,这样前缀和就是01个数的差值
b[i][j]=1;
sum+=a[i][j];
}else{
b[i][j]=-1;
sum-=a[i][j];
}
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+b[i][j];//求前缀和
}
}
int g=0;
for(int i=k;i<=n;i++){//右下角那个点为(i,j)边长为k的正方形
for(int j=k;j<=m;j++){
int t=pre[i][j]-pre[i-k][j]-pre[i][j-k]+pre[i-k][j-k];
g=__gcd(g,t);//关键是推出所有正方形边长为k的前缀和的最大公约数
}
}
//差值是最大公约数的倍数时可以调整使得两类高度总和一样
if((g!=0&&sum%g==0)||sum==0){//初始时差值为零,两边数值相等不需要做操作
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
signed main(){
int T=1;
cin>>T;
while(T--) solve();
return 0;
}