2021.10.05膜你赛
2021.10.05膜你赛
T1
Description
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
小葱同学最近醉心于动态规划的研究,他苦学百年,已经牢牢掌握了加法和减法是怎么运算的。所以小葱同学造了 \(𝑁\) 个数 \(𝑎_1, 𝑎_2, ⋯ , 𝑎_𝑁\),他希望对每个数加上或者减去 \(𝐾\),使得操作之后的最大值减去最小值的差最小。
Solution
考试的时候想出了正解,结果脑子一歪,错失20分。
假设我们给出的序列为 \(\text{1368},k=3\)
先预处理出 \(+k,-k\) 后的答案。 也就是
-2 | 0 | 3 | 5 |
---|---|---|---|
4 | 7 | 10 | 11 |
我们去枚举最大值,也就是第二行,我们的一开始的序列是预先排过序的,假设我们枚举的最大值为 10,那么它往后的肯定不能选择+操作了,但是我们知道即使是减,后面的数也可能会减出一个比我们枚举的更大的,此时真正的最大值为 \(\max(Max_iMin_n,)\) ,因为是让着差最小,我们要让最小值最大,所以当前位置的前面一定都进行加操作,此时的最小值为 \(\min(Max_1,Min_{i+1})\) ,答案就是 \(\min(MAX-MIN)\) 了。
Code
/*
Authour: work by smyslenny
Date: 2021.10.03
Title:
Main idea:
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <queue>
#include <vector>
#include <map>
#define orz cout<<"LKP AK IOI\n"
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
const int M=1e5+5;
int n,k,Ans=INF;
int sz[M];
inline int read()
{
register int x=0,y=1;
register char c=getchar();
while(!isdigit(c)) {if(c=='-') y=0;c=getchar();}
while(isdigit(c)) {x=x*10+(c^48);c=getchar();}
return y?x:-x;
}
namespace substack1{//爆搜
void get_Ans()
{
int Max=-INF,Min=INF;
for(int i=1;i<=n;i++)
{
Max=max(Max,sz[i]);
Min=min(Min,sz[i]);
}
Ans=min(Ans,Max-Min);
}
void dfs(int pos)
{
if(pos>n)
{
get_Ans();
return;
}
sz[pos]+=k;
dfs(pos+1);
sz[pos]-=k;
sz[pos]-=k;
dfs(pos+1);
sz[pos]+=k;
}
void main()
{
dfs(1);
printf("%lld\n",Ans);
}
}
namespace substack2{//k=1
void main()
{
printf("%d\n",sz[n]-sz[1]-2);
}
}
namespace substack3{//自己想的
int MIN=INF,MAX=-INF,Min[M],Max[M];
void main()
{
n=unique(sz+1,sz+1+n)-sz-1;
for(int i=1;i<=n;i++)
Min[i]=sz[i]-k,Max[i]=sz[i]+k;
Ans=sz[n]-sz[1];
for(int i=2;i<n;i++)
{
MAX=max(Max[i],Min[n]);
MIN=min(Max[1],Min[i+1]);
Ans=min(Ans,MAX-MIN);
}
printf("%lld\n",Ans);
}
}
namespace substack4{//正解
int Min[M],Max[M];
void main()
{
n=unique(sz+1,sz+1+n)-sz-1;
Ans=sz[n]-sz[1];
for(int i=1;i<=n;i++)
Min[i]=sz[i]-k,Max[i]=sz[i]+k;
// printf("%d\n",Ans);
for(int i=1;i<=n;i++)
Ans=min(Ans,max(Max[i],Min[n])-min(Max[1],Min[i+1]));
printf("%lld\n",Ans);
}
}
signed main()
{
n=read(),k=read();
for(int i=1;i<=n;i++) sz[i]=read();
sort(sz+1,sz+1+n);
// if(n<=10) substack1::main();
// else
substack3::main();
return 0;
}
/*
3 3
1 3 6
*/
T2
Description
你是能看到第二题的 friends 呢。
——aoao
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
小葱同学自幼对语文非常感兴趣,而小葱同学从小也意识到中文与英文最大的一个区别便是分词。也正是因为中文存在分词的问题,才能诞生像“上海市长江大桥”这种脍炙人口的语句。为了将这种魅力带给英文语句,小葱现在把英文语句里面的空格全部删掉了,于是现在小葱想知道,给定一个英文语句和词表,有多少种对其分词的方案呢?
Solution
设 \(f_i\) 表示字符串前 \(i\) 个字符断句的方案数。
枚举最后一次断句的位置 \(j\),只有\(j+1∼i\) 这段是一个单词,并且 \(f_j\) 这个状态被更新过才能转移。
Code
/*
* @Author: smyslenny
* @Date: 2021.09.
* @Title:
* @Main idea:
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>
#include <map>
#define ll long long
#define INF 0x3f3f3f3f
#define orz cout<<"LKP AK IOI\n"
#define MAX(a,b) (a)>(b)?(a):(b)
#define MIN(a,b) (a)>(b)?(a):(b)
using namespace std;
const int maxn = 210;
const int mod = 1e9 +7;
const int base = 13131;
int read()
{
int x=0,y=1;
char c=getchar();
while(c<'0' || c>'9') {if(c=='-') y=0;c=getchar();}
while(c>='0' && c<='9') { x=x*10+(c^48);c=getchar();}
return y?x:-x;
}
int f[maxn],n,len1,ans[maxn],tot;
char s[maxn],ss[maxn];
map<int,bool> mp;
vector<int> edge[maxn];
void dfs(int u, int tot)
{
ans[tot]=u;
if(u==len1)
{
for(int i=1;i<=len1;i++)
{
cout<<s[i];
if(i!=len1)
for(int j=1;j<=tot;j++)
if(ans[j]==i)
printf("_");
}
printf("\n");
return;
}
for(int i=0,v;i<edge[u].size();i++)
v=edge[u][i],dfs(v,tot+1);
}
signed main()
{
scanf("%s",s+1);
len1=strlen(s+1);
n=read();
for(int i=1;i<=n;i++)
{
scanf("%s",ss+1);
int res=0,len=strlen(ss+1);
for(int j=1;j<=len;j++)
res=(res*base%mod+ss[j]-'a'+1)%mod;
mp[res]=1;
}
f[0]=1;
for(int i=1;i<=len1;i++)
for(int j=1;j<=i;j++)
if(f[j-1])
{
int res=0;
for(int k=j;k<=i;k++)
res=(res*base%mod+s[k]-'a'+1)%mod;
if(!mp[res]) continue;
f[i]=f[i]+f[j-1];
edge[j-1].push_back(i);
}
if(f[len1]!=0)
dfs(0,1);
return 0;
}
T3
Description
你是能看到第三题的 friends 呢。
——laekov
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
小葱同学今天要出门购物,总共有 \(𝑁\) 家商店排成一行,第𝑖家商店的物价为 \(𝑣_𝑖\)。
小葱购物从第 \(𝑎\) 家商店走到第 \(𝑏\) 家商店。在走的过程中,在每家商店小葱可以做下面三件事中的任意一件:
购入一件商品。
卖出一件商品,要求这个时候小葱手上至少有一件商品才能卖出。
路过啥也不干。
现在小葱在行走过程中最多携带一件商品在身上,问假设本金无限的情况下,
小葱从 \(𝑎\) 走到 \(𝑏\) 的过程中最多赚多少钱
Solution
考试的时候想的贪心是当前点大于下一点,卖出,当前点小于下一点,买入,但是是不对的。
价格是一个波动序列,所以根据贪心策略,要在最高点卖,最低点买,这样差就是最大的
所以用树状数组维护区间和
把一个区间里面那些最高点的和和最低点的相反数的和维护出来
Code
/*
30pts:
暴力dp一下
f[i]表示到i的最大价值
100pts
价格是一个波动序列,所以根据贪心策略,要在最高点卖,最低点买,这样差就是最大的
所以用树状数组维护区间和
把一个区间里面那些最高点的和和最低点的相反数的和维护出来
*/
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
char c=getchar();
int x=0,f=1;
while(c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
int n,m;
namespace PT1
{
const int N=1010;
int a[N],f[N];
int solve(int l,int r)
{
memset(f,0,sizeof(f));
if(l<=r)
{
int maxn = -a[l];
for(int i=l+1; i<=r; ++i)
{
maxn=max(maxn,f[i-1]-a[i]);
f[i]=a[i]+maxn;
}
return f[r];
}
else
{
swap(l,r);
int maxn=-a[r];
for(int i=r-1; i>=l; --i)
{
maxn=max(maxn,f[i+1]-a[i]);
f[i]=a[i]+maxn;
}
return f[l];
}
}
int main()
{
for(int i=1; i<=n; ++i) a[i]=read();
while(m--)
{
int opt=read();
int l=read(),r=read();
if(opt==2)
{
a[l]=r;
continue;
}
printf("%d\n",solve(l,r));
}
return 0;
}
}
namespace PT2
{
const int N=100000+100;
int a[N],tree1[N],tree2[N],bz1[N],bz2[N],ma[N],nn;
int lowbit(int x)
{
return x&-x;
}
void update(int *tree,int pos,int c)
{
while(pos<=n)
{
tree[pos]+=c;
pos+=lowbit(pos);
}
}
int query(int *tree,int pos)
{
int ret=0;
while(pos)
{
ret+=tree[pos];
pos-=lowbit(pos);
}
return ret;
}
void updatep_v(int i)
{
if(i<=1||i>=n) return ;
int tmp=query(tree1,i)-query(tree1,i-1);
update(tree1,i,-tmp);
tmp=query(tree2,i)-query(tree2,i-1);
update(tree2,i,-tmp);
bz1[i]=0;
bz2[i]=0;
if(a[i]>a[i-1]&&a[i]>=a[i+1]) bz1[i]=1,update(tree1,i,a[i]);
if(a[i]<=a[i-1]&&a[i]<a[i+1]) bz1[i]=-1,update(tree1,i,-a[i]);
if(a[i]>=a[i-1]&&a[i]>a[i+1]) bz2[i]=1,update(tree2,i,a[i]);
if(a[i]<a[i-1]&&a[i]<=a[i+1]) bz2[i]=-1,update(tree2,i,-a[i]);
}
int main()
{
for(int i=1; i<=n; ++i) a[i]=read();
for(int i=2; i<n; ++i)
{
if(a[i]>a[i-1]&&a[i]>=a[i+1]) bz1[i]=1,update(tree1,i,a[i]);
if(a[i]<=a[i-1]&&a[i]<a[i+1]) bz1[i]=-1,update(tree1,i,-a[i]);
if(a[i]>=a[i-1]&&a[i]>a[i+1]) bz2[i]=1,update(tree2,i,a[i]);
if(a[i]<a[i-1]&&a[i]<=a[i+1]) bz2[i]=-1,update(tree2,i,-a[i]);
}
while(m--)
{
int opt=read();
if(opt==1)
{
int l=read(),r=read();
if(l==r)
{
cout<<0<<'\n';
continue;
}
int ans=0;
if(l<r)
{
if(a[l]<a[l+1]) ans-=a[l];
if(a[r]>a[r-1]) ans+=a[r];
ans+=query(tree1,r-1)-query(tree1,l);
}
else
{
swap(l,r);
if(a[r]<a[r-1]) ans-=a[r];
if(a[l]>a[l+1]) ans+=a[l];
ans+=query(tree2,r-1)-query(tree2,l);
}
printf("%d\n",ans);
}
else
{
int p=read(),w=read();
a[p]=w;
updatep_v(p-1),updatep_v(p),updatep_v(p+1);
}
}
return 0;
}
}
int main()
{
freopen("trade.in","r",stdin);
freopen("trade.out","w",stdout);
n=read(),m=read();
if(n<=1000&&m<=1000) //pt1--dp
{
PT1::main();
}
else
{
PT2::main();//100pts
}
return 0;
}