3.3~3.9
牛客月赛 111
A

贪心,田鸡的最大的a比齐威王次大的v大,田鸡的次大的a比齐威王最小的v大就能赢
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int v[3],a[3];
for(int i=0;i<3;i++)
cin >> v[i];
for(int i=0;i<3;i++)
cin >> a[i];
sort(v,v+3);
sort(a,a+3);
if(a[2] > v[1] && a[1] > v[0])
cout << "Yes\n";
else cout << "No\n";
return 0;
}
B

即使有无限多个且偶数个邪恶英雄合并,也只能得到一个正义英雄,所以贪心,每两个相邻的邪恶英雄合并,就能算出最大的正义英雄
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,sum=0;
string s;
cin >> n >> s;
for(int i=0;i<n;i++){
if(s[i] == 'y') sum++;
else if(s[i] == 'n' && i+1<n && s[i+1] == 'n'){
sum++;
i++;
}
}
cout << sum << '\n';
return 0;
}
C

先滑动窗口求出每个连续相等的区间的连续数目,并给该区间每个元素赋该值;
再将每个非连续区间进行比较,求出最大值
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+1;
vector <string> s(maxn);
int qz[maxn];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,ans=0;
cin >> n;
for(int i=0;i<n;i++)
cin >> s[i];
if(n == 1){
cout << "1\n";
return 0;
}
else if(n == 2){
if(s[0] == s[1]) cout << "2\n";
else cout << "1\n";
return 0;
}
int l=0,r=1,num=1;
while(1){
if(r == n){
for(int i=l;i<r;i++)
qz[i] = num;
break;
}
if(s[l] == s[r]){
r++;
num++;
}
else{
for(int i=l;i<r;i++)
qz[i] = num;
num=1;
l = r;
r++;
}
}
ans = max(ans,qz[1]);
ans = max(ans,qz[2]);
for(int i=2;i<n;i++){
ans = max(ans,qz[i]);
if(s[i] == s[i-2] && s[i] != s[i-1])
ans = max(ans,qz[i-2]+qz[i]);
}
cout << ans << '\n';
return 0;
}
D

DP,这题有墙壁,墙壁的优先级大于自己。到每个点需要的回合是x+y-2,如果x+y-2>=变成墙壁需要的回合数,那么就给这个点权赋极小值,相当于不通了。
注意判断变墙的回合数的数组要初始化最大值
状态转移方程
dp[i][j] = max(dp[i-1][j],dp[i][j-1] + room[i][j])
#include <bits/stdc++.h>
using namespace std;
const int minn = -1e8;
int n,m,t,x,y,v,ans=0;
int dp[1002][1002]={0},room[1002][1002]={0},change[1001][1001]={0};
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin >> room[i][j];
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
dp[i][j] = -1e8;
change[i][j] = 1e8;
}
}
cin >> t;
for(int i=0;i<t;i++){
cin >> x >> y >> v;
change[x][y] = v;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i+j-2 >= change[i][j])
room[i][j] = minn;
}
}
ans = 0;
dp[0][1] = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j] = max(dp[i-1][j],dp[i][j-1])+room[i][j];
ans = max(ans,dp[i][j]);
}
}
/*for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout << dp[i][j];
cout << '\n';
}*/
cout << ans << '\n';
return 0;
}
牛客周赛83
A B

签到
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
char inp;
cin >> inp;
if(inp == 'U' || inp == 'D')
cout << "R\n";
else cout << "U\n";
return 0;
}

#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
for(int i=1;i<=n;i++)
cout << (i%2 ? 1:2) << ' ';
cout << '\n';
}
return 0;
}
C

构造,还是要点思维的,有坑。
构造一个“xx”形式的即可,如x=23,那么构造2323,2323 = 23*100+23,y就是101
坑点就在于pow函数那个地方要开long long,否则会爆
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin >> t;
while(t--){
string s;
cin >> s;
int si = s.size();
cout << (long long)pow(10,si)+1 << '\n';
}
return 0;
}
E

DP
dp[i][j]表示移动到第i格花了j步
考虑来的方向:
dp[i-6][j-1] -> dp[i][j]
dp[i-5][j-1] -> dp[i][j]
dp[i-4][j-1] -> dp[i][j]
dp[i-3][j-1] -> dp[i][j]
dp[i-2][j-1] -> dp[i][j]
dp[i-1][j-1] -> dp[i][j]
因此遍历每个i j,注意从i-6这里可能会小于0,因此l = max(0ll,i-6)
状态转移方程为dp[i][j] = max(dp[i][j,dp[l][j-1]+a[i])
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int inf = -1e18;
const int sup = 1e4+1;
const int mid = 1e3+1;
const int maxn = 1e9+1;
int n,k;
vector<vector<int>> dp(sup,vector<int>(mid,inf));
int a[sup];
//dp[i][j]表示移动到第i格花了j步
void solve(){
cin >> n >> k;
for(int i=1;i<=n;i++)
cin >> a[i];
int ans = inf;
dp[0][0] = 0ll;
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,k);j++)
for(int l=max(0ll,i-6);l<=i-1;l++)
dp[i][j] = max(dp[i][j],dp[l][j-1]+a[i]);
ans = max(ans,dp[i][k]);
}
cout << ans << '\n';
return;
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
牛客周赛84
A
题目

签到
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int a,b,c;
cin >> a >> b >> c;
if(a==b && b==c) cout << "Yes\n";
else cout << "No\n";
return 0;
}
B
题目

贪心,当数组是郑旭序列或倒序序列时,陡峭值是最小的。
数组元素相同时,正序倒序都一样,1个
数组元素不同是,正序倒序不一样,2个
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
int a[101];
cin >> n;
for(int i=0;i<n;i++)
cin >> a[i];
sort(a,a+n);
int pd=1,minn=0;
for(int i=0;i<n-1;i++){
minn += abs(a[i+1]-a[i]);
if(a[i+1] != a[i]) pd=0;
}
cout << (pd ? 1:2) << ' ' << minn << '\n';
return 0;
}
C
题目

由于n*k = 1e6,可以直接暴力模拟
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,k;
cin >> n >> k;
string s;
cin >> s;
int sum=0;
for(int i=0;i+k<=n;i++){
//cout << "i:"<<i<<'\n';
for(int j=0;j<k-1;j++){
//cout << "j:"<<j<<'\n';
sum += abs(s[i+j+1]-s[i+j]);
//cout << sum << '\n';
}
}
cout << sum << '\n';
return 0;
}
D
题目

n*k = 1e12,无法像上题一样暴力速解
想到了一个复杂度O(n)的办法
先做一个k=2的相邻元素之间的陡峭值数组,可以发现,随着k的变化,陡峭值数组的每个元素被加的次数呈现出对称性
且最多被加的次数可以算出来是int maxt = (k<=n/2+1 ? k-1:n-k+1)
因此走一遍数组,sum += vec[i]*times,即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
vector <int> vec;
signed main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,k;
cin >> n >> k;
string s;
cin >> s;
if(n==2){
cout << abs(s[1]-s[0]) << '\n';
return 0;
}
int maxt = (k<=n/2+1 ? k-1:n-k+1);
//cout<<"maxt:"<<maxt<<'\n';
for(int i=0;i+1<n;i++){
vec.push_back(abs(s[i+1]-s[i]));
}
//cout<<"vec:\n";
//for(auto i:vec) cout<<i<<' ';
//cout << '\n';
int si = n-1,sum = 0;
int mid = si/2-1;
int times = 1;
//cout<<"si:"<<si<<" mid:"<<mid<<'\n';
for(int i=0;i<=mid;i++){
sum += vec[i]*times;
sum += vec[si-i-1]*times;
if(times < maxt) times++;
}
if(si%2) sum+=vec[si/2]*maxt;
cout << sum << '\n';
return 0;
}
周赛84的周日才打,先补到这,剩下的下周来
线段树
线段树的核心思想是分治,大区间的解可以由小区间的解合并而来,总复杂度为O(nlogn)。有两个基本应用场景:
- 区间最值问题。长为n的序列a,需要以下操作:
- 求区间[ i , j ]内的最值
- 修改a[k]为x
- 区间和问题。长为n的序列a,先更改某些数的值,再求区间[ i , j ]的和。
线段树建立在二叉树上,每次分治左右子树各一半,能利用二叉树的许多性质。
PS:对于任意一个节点 i ,其父亲节点是 i/2 , 其左孩子是 2i , 其右孩子是 2i+1 。
考察每个线段[L,R],L是左端,R是右端:
- L=R,说明这个节点只有一个元素,它是叶子节点。
- L<R,说明这个节点代表的不止一个点,那么它有两个孩子,左孩子区间为[L,M],右孩子区间为[M+1,R],其中M = (L+R)/2。
线段树的构造:
//定义根节点是tree[1],即编号为1的节点是根
int tree[N*4]; //用tree[i]记录线段i的最值或区间和
//p是父节点,ls(p)是左儿子,rs(p)是右儿子
int ls(int p){return p<<1;} //左儿子,编号是p*2
int rs(int p){return p<<1|1;} //右儿子,编号是p*2+1
void push_up(int p){ //从下向上传递区间值
tree[p] = tree[ls(p)] + tree[rs(p)]; //区间和
//tree[p] = min(tree[ls(p)],tree[rs(p)]); //最小值
}
void build(int p,int pl,int pr){ //节点编号p指向区间[pl,pr]
if(pl == pr) {tree[p] = a[pl];return;} //最底层的叶子节点,存叶子节点的值
int mid = (pl+pr) >> 1; //分治:折半
build(ls(p),pl,mid); //递归左儿子
build(rs(p),mid+1,pr); //递归右儿子
push_up(p); //从下往上传递区间值
}
区间查询:
int query(int L,int R,int p,int pl,int pr){
if(L<=pl && R>=pr) return tree[p]; //完全覆盖
int mid = (pl+pr) >> 1;
if(L<=mid) res += query(L,R,ls(p),pl,mid); //L与左子节点有重叠
if(R>=mid+1) res += queru(L,R,rs(p),mid+1,pr); //R与右子节点有重叠
return res;
} //调用方式 query(L,R,1,1,n);
Layz-Tag:
线段树的节点tree[i]记录区间 i 的值,那么可以再定义一个tag[i],用它统一记录区间 i 的修改。每次修改的复杂度为O(log2n),一共m次操作,总复杂度O(mlog2n)
区间修改update()
先找到具体区间,对该节点即以上的节点做修改,并给该节点打tag。下次经过某节点且该节点tag不为0,那么就要把tag[p]传递给左右子树,并清空tag[p],这个过程用push_down()函数完成。
模板(洛谷 P3372)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
ll a[N]; //记录题目给出的数列的元素,从a[1]开始
ll tree[N<<2]; //tree[i]为第i个节点的值,表示一个线段的值,如最值,区间和
ll tag[N<<2]; //tag[i]为第i个节点的Lazy-Tag,统一记录这个区间的修改
ll ls(ll p){return p<<1;} //定位左儿子: p*2
ll rs(ll p){return p<<1|1;} //定位右儿子: p*2+1
void push_up(ll p){ //从下向上传递区间值
tree[p] = tree[ls(p)] + tree[rs(p)]; //求区间和
//tree[p] = min(tree[ls(p)],tree[rs(p)]); 求最小值
}
void build(ll p,ll pl, ll pr){//建树。p为节点编号,指向区间[pl,pr]
tag[p] = 0; //Lazy-Tag标记
if(pl == pr){tree[p] = a[pl];return;} //最底层的叶子,赋值
ll mid = (pl + pr) >> 1; //分治,折半
build(ls(p),pl,mid); //左儿子
build(rs(p),mid+1,pr); //右儿子
push_up(p); //从下往上传递区间值
}
void addtag(ll p,ll pl,ll pr,ll d){ //给节点ptag标记,并更新tree
tag[p] += d; //打上tag标记
tree[p] += d*(pr-pl+1); //计算新的tree
}
void push_down(ll p,ll pl,ll pr){ //不能覆盖时,把tag传给子树
if(tag[p]){ //有tag标记,以前修改留下的
ll mid = (pl+pr)>>1;
addtag(ls(p),pl,mid,tag[p]); //tag传给左子树
addtag(rs(p),mid+1,pr,tag[p]); //tag传给右子树
tag[p] = 0; //自己的tag清零
}
}
void update(ll L,ll R,ll p, ll pl,ll pr,ll d){ //区间修改,每个元素加d
if(L<=pl && pr<=R){ //完全覆盖,直接返回这个节点,它的子树不用继续再深入
addtag(p,pl,pr,d); //给p节点打标记,下一次修改会用到
return;
}
push_down(p,pl,pr); //如果不能覆盖,就把tag传给子树
ll mid = (pl+pr)>>1;
if(L <= mid) update(L,R,ls(p),pl,mid,d); //递归左子树
if(R > mid) update(L,R,rs(p),mid+1,pr,d); //递归右子树
push_up(p); //更新
}
ll query(ll L,ll R,ll p,ll pl,ll pr){
//查询区间[L,R],P是当前节点(线段)的编号,[pl,pr]是节点p表示的线段区间
if(pl >= L&&R >= pr) return tree[p]; //完全覆盖,直接返回
push_down(p,pl,pr); //不能覆盖,递归子树
ll res = 0;
ll mid = (pl+pr)>>1;
if(L<=mid) res+=query(L,R,ls(p),pl,mid); //左子节点有重叠
if(R>mid) res+=query(L,R,rs(p),mid+1,pr); //右子节点有重叠
return res;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll n,m;
cin >> n >> m;
for(ll i=1;i<=n;i++)
cin >> a[i];
build(1,1,n); //建树
while(m--){
ll q,L,R,d;
cin >> q;
if(q == 1){ //区间修改,每个元素加d
cin >> L >> R >> d;
update(L,R,1,1,n,d);
}
else{ //区间查询,[L,R]区间和
cin >> L >> R;
cout << query(L,R,1,1,n) << '\n';
}
}
return 0;
}

浙公网安备 33010602011771号