牛客周赛 Round 68 个人题解 (A~F)
牛客周赛 Round 68
解题思路
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
void solve(){
int n;cin>>n;
string s;cin>>s;
s=" "+s;
int ans=0;
for(int i=1;i<=n;i++){
if(s[i]=='0') ans++;
}
cout<<ans<<endl;
}
int main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}
解题思路
- 太久没做题了,二维前缀和公式不会推,难绷
- n4枚举左上角和右下角端点,然后二位前缀和预处理即可,二维前缀和公式为sum[x][y]-sum[x][j-1]-sum[i-1][y]+sum[i-1][j-1]
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
char g[40][40];
int sum[40][40];
void solve(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>g[i][j];
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
if(g[i][j]=='.') sum[i][j]++;
}
}
int ans=0;
int l1,r1,l2,r2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int x=i;x<=n;x++){
for(int y=j;y<=m;y++){
int t=sum[x][y]-sum[x][j-1]-sum[i-1][y]+sum[i-1][j-1];
if(t==(x-i+1)*(y-j+1) && t>ans){
ans=max(ans,t);
l1=i,r1=j,l2=x,r2=y;
}
}
}
}
}
cout<<l1<<" "<<r1<<" "<<l2<<" "<<r2<<endl;
}
int main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}
题目分析
- 先计算两个数组内部自己相同的数,这些是至少要操作的部分,即为操作1,然后统计两边共有的数字的个数,这部分的数字删除有两种途径,一种是跟着操作1删除,一种是两边内部没有相同的数之后,同时相同,计算这几部分的值相加即可
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N],b[N];
void solve(){
int n;cin>>n;
map<int,int> mpa;
map<int,int> mpb;
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++){
cin>>a[i];
mpa[a[i]]++;
}
for(int i=1;i<=n;i++){
cin>>b[i];
mpb[b[i]]++;
}
for(auto [x,y]:mpa) cnt1+=y-1;
for(auto [x,y]:mpb) cnt2+=y-1;
int ans=0;
if(cnt1>cnt2){
int tot=0;
int d=cnt1-cnt2;
for(auto [x,y]:mpa){
if(mpb[x]) tot++;
}
if(tot<=cnt1-cnt2) ans=cnt1;
else ans=cnt1+(tot-d+1)/2;
}
else{
int tot=0;
int d=cnt2-cnt1;
for(auto [x,y]:mpb){
if(mpa[x]) tot++;
}
if(tot<=d) ans=cnt2;
else ans=cnt2+(tot-d+1)/2;
}
cout<<ans<<endl;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}
题目分析
- 495=3*3*5*11,所以只需要同时含有这四个质因子,则满足乘积为495的倍数,我们状压处理四位表示每一位是否含有这四个质因子,那么两数相乘若i|j==15,则满足题意,还有一种情况是1101 | 0001,这也是满足情况的
- 即当i|j==13 且i,j第一位均为1时满足题意
- 加1操作怎么处理呢?考虑记录前缀和与后缀和,先计算不进行加1操作的答案,枚举a[i],只需枚举j使得calc(i)与j满足上述两种情况之1,则ans+=pre[i-1][j]
- 那么枚举+1的数,然后通过前后缀快速计算,新增加的贡献
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=4e5+10;
int n;
int a[N],v[N],pre[N][20],suf[N][20];
int calc(int x){
int res=0;
if(x%3==0) res|=1;
if(x%9==0) res|=2;
if(x%5==0) res|=4;
if(x%11==0) res|=8;
return res;
}
bool check(int j,int x){
return (j|x)==15 || ( (j|x)==13 && ((j&1) && (x&1)) );
}
void init(){
for(int i=1;i<=n;i++) v[i]=calc(a[i]);
for(int i=1;i<=n;i++){
for(int j=0;j<=15;j++){
pre[i][j]=pre[i-1][j];
}
pre[i][v[i]]++;
}
for(int i=n;i>=1;i--){
for(int j=0;j<=15;j++){
suf[i][j]=suf[i+1][j];
}
suf[i][v[i]]++;
}
}
void solve(){
cin>>n;
int ans=0;
for(int i=1;i<=n;i++) cin>>a[i];
init();
for(int i=1;i<=n;i++){
for(int j=0;j<=15;j++){
if(check(j,v[i])) ans+=pre[i-1][j];
}
}
int maxn=0;
for(int i=1;i<=n;i++){
int sum=0;
int x=a[i];
x++;
int tmp=calc(x);
for(int j=0;j<=15;j++){
if(check(j,v[i])){
sum=sum-pre[i-1][j]-suf[i+1][j];
}
if(check(j,tmp)){
sum=sum+pre[i-1][j]+suf[i+1][j];
}
}
maxn=max(maxn,sum);
}
cout<<ans+maxn<<endl;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}
解题思路
- 分组背包,第一次做
- f[i][j]表示前i个数总和为j,状态转移则为f[i][j]=f[i-1][j],枚举j=a[i],每次除以2,枚举总和k,k>=j,则f[i][k] |= f[i-1][k-j]
- 最后dp完再倒序找方案即可,详细见代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
const int MAXN=1e5;
int a[110],f[110][N],ans[110];
void solve(){
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=MAXN;j++) f[i][j]=f[i-1][j];
for(int j=a[i];j>=1;j/=2){
for(int k=MAXN;k>=j;k--){
f[i][k]|=f[i-1][k-j];
}
}
}
if(!f[n][MAXN]){
cout<<-1<<endl;
return;
}
int res=MAXN;
for(int i=n;i>=1;i--){
if(f[i][res]==f[i-1][res]){
ans[i]=100;
continue;
}
for(int j=a[i],cnt=0;j>=1;j/=2,cnt++){
if(res-j<0) continue;
if(f[i][res]==f[i-1][res-j]){
res-=j;
ans[i]=cnt;
break;
}
}
}
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}
解题思路
- 树上dp,我们记f[u][i]表示结点u为路径的一个端点,且路径总和不超过i的方案总数
- 考虑图片上的两种情况,对于情况1,可以直接从v的状态转移到u的状态来,对于情况2,当前点会由两个儿子的值得到,这里运用乘法原理,不好描述,详细看代码吧
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int n,ans=0;;
int a[N],f[N][15];
vector<int> g[N];
void dfs(int u,int fa){
vector<int> s(10); //当前层下的路径数
int now=a[u];
for(auto v:g[u]){
if(v==fa) continue;
dfs(v,u);
//情况1的直接转移,即u为一条路径的终点状态
for(int i=now;i<=9;i++){
f[u][i]+=f[v][i-now];
}
//情况2,此时点u作为一条路径上的点(非端点)
for(int i=0;i<=9;i++){
for(int j=0;j<=9-i-now;j++){
ans+=f[v][i]*s[j];
}
}
//记录当前层下的点
for(int i=0;i<=9;i++) s[i]+=f[v][i];
}
//点u作为终点
for(int i=0;i<=9;i++) ans+=f[u][i];
f[u][now]++;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n-1;i++){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
cout<<ans<<endl;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T=1;
while(T--) solve();
return 0;
}