AtCoder abc159 - AtCoder Beginner Contest 159
没想到昨天的ABC竟是如此之水。而我却没打,真是太可惜了/kk
AtCoder比赛页面传送门
A - The Number of Even Pairs
AtCoder题目页面传送门
有\(n+m\)个整数,其中\(n\)个是偶数,\(m\)个是奇数。求有多少种不同的选\(2\)个不同的数的方案,使得这\(2\)个数之和是偶数。
显然,\(2\)个数之和是偶数当且仅当它们奇偶性相同。选\(2\)个不同的偶数有\(\dfrac{n(n-1)}2\)种方案,选\(2\)个不同的奇数有\(\dfrac{m(m-1)}2\)种方案,共\(\dfrac{n(n-1)}2+\dfrac{m(m-1)}2\)种方案。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
cout<<n*(n-1)/2/*选2个不同的偶数*/+m*(m-1)/2/*选2个不同的奇数*/;
return 0;
}
B - String Palindrome
AtCoder题目页面传送门
定义一个长度为奇数的字符串\(a\)是强回文的当且仅当\(a,a_{1\sim \frac{n-1}2},a_{\frac{n+3}2\sim |a|}\)这\(3\)个字符串都是回文的。给定一个长度为奇数的字符串\(a\),判断它是否强回文。
根据强回文的定义判断即可。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N=99;
int n;
char a[N+5];
bool pal(int l,int r){//[a[l~r]是回文的]
while(l<=r){
if(a[l]!=a[r])return false;
l++;r--;
}
return true;
}
int main(){
cin>>a+1;
n=strlen(a+1);
puts(pal(1,n)&&pal(1,n-1>>1)&&pal(n+3>>1,n)?"Yes":"No");//定义
return 0;
}
C - Maximum Volume
AtCoder题目页面传送门
给定一个整数\(x\)。求所有各棱长为实数且和为\(x\)的长方体中最大的体积是多少。
原问题即给定\(x\),求\(\max\limits_{a+b+c=x}\{abc\}\)。根据和定差小积大,当\(a=b=c=\dfrac x3\)时,\(abc\)最大为\(\left(\dfrac x3\right)^3\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
double x;
cin>>x;
printf("%.100lf",pow(x/3,3));
return 0;
}
D - Banned K
洛谷题目页面传送门 & AtCoder题目页面传送门
给定\(n\)个整数,第\(i\)个数为\(a_i\)。求\(\forall i\in[1,n]\),不能选\(a_i\)的情况下,有多少种不同的选\(2\)个相等的数的方案。
\(n\in\left[3,2\times10^5\right],a_i\in[1,n]\)。
由于\(a_i\in[1,n]\),所以我们可以直接把数们放进桶里,\(buc_i=\sum\limits_{j=1}^n[a_j=i]\)。这样,\(\forall i\in[1,n]\),\(i\)的答案显然是\(\sum\limits_{j=1}^n\dfrac{(buc_j-[a_i=j])(buc_j-[a_i=j]-1)}2\)。但这样时间复杂度显然是\(\mathrm O\!\left(n^2\right)\)。于是考虑先算出不带“不能选某数”的限制时的答案\(ans=\sum\limits_{i=1}^n\dfrac{buc_i(buc_i-1)}2\),\(\mathrm O(n)\),然后\(\forall i\in[1,n]\),从\(ans\)里减去必须选\(a_i\)时的方案数,答案为\(ans-(buc_{a_i}-1)\)。时间复杂度\(\mathrm O(n)\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=200000;
int n;
int a[N+1];
int buc[N+1];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],buc[a[i]]++/*装进桶*/;
int ans=0;
for(int i=1;i<=n;i++)ans+=buc[i]*(buc[i]-1)/2;//不带限制时的答案
for(int i=1;i<=n;i++)cout<<ans-(buc[a[i]]-1)<<"\n";
return 0;
}
E - Dividing Chocolate
洛谷题目页面传送门 & AtCoder题目页面传送门
给定一个\(n\times m\)的01字符矩阵\(a\)。求最少需要多少次水平或竖直切割,使得切出的每一块都包含不超过\(s\)个\(\texttt1\)。
\(n\in[1,10],m\in[1,1000]\)。
看到\(n\)如此之小,不难想到暴力枚举水平切割的\(2^{n-1}\)种状态。对于每一种状态,从左往右贪心,每到一列就将水平切割切成的每一块当前的\(\texttt1\)的个数增加若干,如果至少有\(1\)块当前的\(\texttt1\)的个数超过了\(s\),便在本列与上一列之间竖直切割一刀,如果仍然超过,那么此种水平切割状态就不能要。贪心的正确性显然。对于每种水平切割状态,如果能要的话更新答案即可。时间复杂度\(\mathrm O(2^nnm)\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int inf=0x3f3f3f3f;
const int N=10,M=1000;
int n,m,s;
char a[N+1][M+5];
int main(){
cin>>n>>m>>s;
for(int i=1;i<=n;i++)cin>>a[i]+1;
int ans=inf;
for(int i=0;i<1<<n-1;i++){//暴力枚举2^(n-1)种水平切割状态
vector<int> pos;//水平切割处
pos.pb(0);
for(int j=1;j<n;j++)if(i&1<<j-1)pos.pb(j);
pos.pb(n);
int now=pos.size()-2;//此水平切割状态当前切割次数
vector<int> cnt(pos.size()-1,0);//水平切割的每一块当前'1'的个数
for(int j=1;j<=m;j++){//从左往右贪心
bool ok=true;
vector<int> cnt0(pos.size()-1,0);//本列水平切割的每一块'1'的个数
for(int k=0;k<cnt.size();k++){//枚举水平切割的每一块
for(int o=pos[k]+1;o<=pos[k+1];o++)cnt0[k]+=a[o][j]^48;
if(cnt0[k]>s)goto label_end;//不能要
if(cnt[k]+cnt0[k]>s)ok=false;//需要竖直切割
}
if(ok)for(int k=0;k<cnt.size();k++)cnt[k]+=cnt0[k];//不竖直切割
else now++,cnt=cnt0;//竖直切割
}
ans=min(ans,now);//更新答案
label_end:;
}
cout<<ans;
return 0;
}
F - Knapsack for All Segments
洛谷题目页面传送门 & AtCoder题目页面传送门
给定\(n\)个整数,第\(i\)个数为\(a_i\)。定义\(f(l,r)\)表示\([l,r]\)内和为\(m\)的集合的数量。求\(\sum\limits_{i=1}^n\sum\limits_{j=i}^nf(i,j)\)。答案对\(998244353\)取模。
\(n,m,a_i\in[1,3000]\)。
考虑算贡献法。对于某个集合\(\{a_{x_i}\mid i\in[1,k]\}\),其中\(x_1<x_2<\cdots<x_k\),区间\([l,r]\)包含它当且仅当\(l\in[1,x_1],r\in[x_k,n]\),所以此集合对答案的贡献为\(x_1(n-x_k+1)\)。
看到“和为某数”一类的问题,不难想到类似背包的值域DP。设\(dp_{i,j}\)表示考虑到第\(i\)个数,所有和为\(j\)的集合最左边的位置之和。边界是\(dp_{0,i}=0\),目标是\(\sum\limits_{i=1}^n\begin{cases}0&m<a_i\\i(n-i+1)&m=a_i\\dp_{i-1,m-a_i}(n-i+1)&m>a_i\end{cases}\)(枚举和为\(m\)的集合最右边的位置贡献答案),状态转移方程是\(dp_{i,j}=dp_{i-1,j}+[j=a_i]i+\begin{cases}dp_{i-1,j-a_i}&j\geq a_i\\0&j<a_i\end{cases}\)。时间复杂度\(\mathrm O(nm)\)。
另外,此状态转移方程与01背包极其相似,可以用类似01背包的方式将DP数组压成一维,并在每次用\(a_i\)更新\(dp'\)之前及时将\(i\)作为和为\(m\)的集合最右边的位置贡献答案。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=3000,M=3000;
int n,m;
int a[N+1];
int dp[M+1];//dp'
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
int ans=0;
for(int i=1;i<=n;i++){
if(m==a[i])(ans+=1ll*i*(n-i+1)%mod)%=mod;
else if(m>a[i])(ans+=1ll*dp[m-a[i]]*(n-i+1)%mod)%=mod;//更新答案
for(int j=m;j>=a[i];j--)(dp[j]+=dp[j-a[i]]+(j==a[i]?i:0))%=mod;//转移
}
cout<<ans;
return 0;
}