A
区间修改,单调查询
典型的线段树
这个就不说了吧…lazy标记入门题
struct node{
ll l,r;
ll val,be;
}tree[maxn<<2];
void build(ll rt,ll l,ll r){
tree[rt].l=l,tree[rt].r=r;
tree[rt].be=-1;
tree[rt].val=-1;
if(l==r){
tree[rt].val=read();
return ;
}
build(lson);
build(rson);
}
inline void push_down(ll rt){
ll l=tree[rt].l,r=tree[rt].r;
if(tree[rt].be!=-1&&l!=r){
tree[ls].val=tree[rt].be;
tree[rs].val=tree[rt].be;
tree[ls].be=tree[rt].be;
tree[rs].be=tree[rt].be;
tree[rt].be=-1;
}
}
void updata(ll rt,ll a,ll b,ll c){
ll l=tree[rt].l,r=tree[rt].r;
if(l==a&&r==b){
tree[rt].val=c;
tree[rt].be=c;
return ;
}
push_down(rt);
if(b<=md){
updata(ls,a,b,c);
}else if(a>=md+1){
updata(rs,a,b,c);
}else{
updata(ls,a,md,c);
updata(rs,md+1,b,c);
}
}
ll query(ll rt,ll l,ll r,ll pos){
if(pos>=l&&pos<=r&&tree[rt].be!=-1){
return tree[rt].val;
}
if(l==pos&&r==pos){
return tree[rt].val;
}
push_down(rt);
if(pos<=md){
return query(lson,pos);
}else{
return query(rson,pos);
}
}
void solve(){
ll n=read(),m=read();
build(1,1,n);
while(m--){
ll ope=read();
if(ope==1){
ll a=read(),b=read(),c=read();
updata(1,a,b,c);
}else{
ll pos=read();
cout<<query(1,1,n,pos)<<endl;
}
}
}
B
emmmm…签到题吧,但我第一眼没看出来,读完没啥思路,暴力肯定不可以…还以为是啥高深的数据结构就直接放了…去看后面,最后再回来看的时候发现其实挺简单的…
我的思路是开个vector储存,对于入队直接push_back,出队的时候,先判断size是否大于等于k,有的话就利用vector数组的erase函数,进行操作
先输出v[sz-k],代表这个位置的值,然后这个位置的地址就是v.end()-k
定义一个迭代器为it=v.end()-k,然后v.erase(it)就可以了
vector<int>v;
void solve(){
char str[10];
int n=read(),k=read();
int sz=0;
rep(i,1,n){
scanf("%s",str);
if(str[0]=='i'){
int pos=read();
v.pb(pos);
sz++;
}else{
if(sz<k){
puts("error!");
}else{
int val=v[sz-1-k+1];
printf("%d\n",val);
vector<int>::iterator it=v.end()-k;
v.erase(it);
v.resize(sz-1);
sz--;
}
}
}
}
但是赛后讲题的时候,hhw学长给出了一个更好的思路
维护一个栈和队列,队列里面最多只存k-1个数,其余的放在栈里面,每次加一个数就直接加进队列,如果当前队列大小超过k-1,就一个个pop进栈,这样栈的顶端永远都是第k个数,如果栈为空则代表不存在第k个数,妙啊
void solve(){
char str[10];
int n=read(),k=read();
int sz=0;
rep(i,1,n){
scanf("%s",str);
if(str[0]=='i'){
int pos=read();
que.push(pos);
sz++;
if(sz>k-1){
sta.push(que.front());
que.pop();
sz--;
}
}else{
if(sta.empty()){
puts("error!");
}else{
printf("%d\n",sta.top());
sta.pop();
}
}
//cout<<str<<endl;
}
}
C
题意很简单,就是跑一圈再回来,看到这个题就想到了dfs和状压DP…
正解是状压DP,但是我第一眼就否定了状压,因为我以前写过的状压n的大小都是15左右,21这么大的状压我没搞过…潜意识以为不可以就放弃了这种做法,写了一个dfs+剪支,然后T掉了…然后果断溜了,最后回来看的时候,想着说不定状压可以过呢?就码来码去写了一个状压DP。。然后真过了…
我吐了,赛后问学长才知道dfs大概到15,状压大概到21,nb的状压写法可以写到25…我惊了…是我太菜了,知识漏洞导致第一时间放弃了正解…
先贴一个dfs+剪支,T掉的算法
int xx[25],yy[25];
int dis[25][25];
int vis[25];
int ans;
void dfs(int now,int num,int tot){
if(tot+dis[now][1]>=ans){
return ;
}
if(num==n){
ans=min(ans,tot+dis[now][1]);
return ;
}
for(int i=1;i<=n;i++){
if(!vis[i]){
vis[i]=1;
dfs(i,num+1,tot+dis[now][i]);
vis[i]=0;
}
}
}
void solve(){
scanf("%d",&n);
rep(i,1,n){
scanf("%d %d",&xx[i],&yy[i]);
}
rep(i,1,n){
rep(j,1,n){
dis[i][j]=abs(xx[i]-xx[j])+abs(yy[i]-yy[j]);
}
}
ans=1e9;
ms(vis,0);
vis[1]=1;
dfs(1,1,0);
printf("%d\n",ans);
}
然后是状压DP的ac写法
int n;
int xx[25],yy[25];
int dis[25][25];
int dp[1<<21][22];
void solve(){
scanf("%d",&n);
rep(i,1,n){
scanf("%d %d",&xx[i],&yy[i]);
}
if(n==1){
puts("0");
return ;
}
rep(i,1,n){
rep(j,1,n){
dis[i][j]=abs(xx[i]-xx[j])+abs(yy[i]-yy[j]);
}
}
for(int i=1;i<=(1<<n)-1;i++){
for(int j=1;j<=n;j++){
dp[i][j]=inf;
}
}
dp[1][1]=0;
int ed=((1<<n)-1);
for(int i=1;i<=ed;i++){
for(int j=1;j<=n;j++){
if((1<<j-1)&i){
for(int k=1;k<=n;k++){
if(k==j){
continue;
}
if(i&(1<<k-1)){
dp[i][j]=min(dp[i][j],dp[i^(1<<j-1)][k]+dis[k][j]);
}
}
}
}
}
int ans=INF;
for(int i=2;i<=n;i++){
ans=min(ans,dp[ed][i]+dis[i][1]);
}
cout<<ans<<endl;
}
(状压松弛的时候写错了一次…wa…好久没写状压了…
D到F 还没做出来
D说是一个简单题,我也觉得是简单题,但是一直没过,待会去找学长要一下wa的数据,,说不定真是我读错题了…
先跳过这几个题目
-------------------------------------------
D出来了
原因是我的reverse函数写反了…
很简单的背包问题,首先我们输入价值然后从大到小排序,记录一个前缀和表示前i个物品最多可以卖多少钱,然后背包就是dp[N]代表每个体积最小花费多少钱,之后枚举每一种体积,求出价钱减去花费的最大值为多少
ll arr[maxn<<1],sum[maxn<<1];
ll dp[maxn<<1];
struct node{
ll p,c;
}input[maxn<<1];
bool cmp(ll a,ll b){
return a>b;
}
void solve(){
ll n=read(),m=read();
rep(i,1,n){
arr[i]=read();
}
sort(arr+1,arr+n+1,cmp);
sum[0]=0;
rep(i,1,n){
sum[i]=sum[i-1]+arr[i];
}
ll ed=1e4;
rep(i,1,m){
input[i].p=read();
input[i].c=read();
//ed+=input[i].p;
}
dp[0]=0;
rep(i,1,ed){
dp[i]=1e18;
}
rep(i,1,m){
ll num=input[i].p;
ll cost=input[i].c;
for(ll j=ed;j>=1;j--){
if(j>num){
if(dp[j-num]!=1e18){
dp[j]=min(dp[j],dp[j-num]+cost);
}
}else{
dp[j]=min(dp[j],cost);
}
}
}
ll ans=0;
rep(i,1,n){
for(ll j=i;j<=ed;j++){
if(dp[j]!=1e18){
ans=max(ans,sum[i]-dp[j]);
}
}
}
printf("%lld\n",ans);
}
G
并查集+01背包
毋庸置疑的是需要写一个并查集来计算每个集合的数量,这点是肯定的
然后这个题目说的是一共n个小医院,要求平分成两个集合,
先把n为奇数给特判掉…
处理完后,是否可以平分就转换成了
给出你几个数(每个集合的大小),求问是否可以分成两个大小一致的大集合,也就是说是否可以抽出一部分数使得这些数之和为n/2
能找出来的话,就代表剩下为n-n/2,由于奇数特判掉了,所以这样必为平分
那就是很明显的01背包问题了…
01背包简单题(关键在于怎么看出这是一个背包
然后就是很常规的01背包倒着推,完全背包正着推了(没懂的话可以私信我
贴代码吧
ll dad[maxn],ank[maxn];
ll seek(ll x){
if(x==dad[x]){
return x;
}else{
return dad[x]=seek(dad[x]);
}
}
vector<ll>v;
ll dp[maxn];
void solve(){
ll n=read(),m=read();
rep(i,1,n){
dad[i]=i;
ank[i]=1;
}
while(m--){
ll a=read(),b=read();
ll fa=seek(a),fb=seek(b);
dad[fa]=fb;
ank[fb]+=ank[fa];
}
if(n&1){
puts("NO");
return ;
}
rep(i,1,n){
if(seek(i)==i){
v.pb(ank[i]);
}
}
ms(dp,0);
dp[0]=1;
ll sz=v.size();
for(ll i=0;i<=sz-1;i++){
ll val=v[i];
if(val>n/2){
continue;
}
for(ll j=n/2;j>=val;j--){
if(dp[j-val]){
dp[j]=1;
}
}
}
if(dp[n/2]){
puts("YES");
}else{
puts("NO");
}
}
H
很明显这种问题是dp
没看出来也要学着把没思路的题都当成dp
然后安慰自己,我是dp蒟蒻,不会写正常
然后就可以保持好的心态来换一个题继续wa
是一个dp问题,二维dp,dp[i][j]
代表前i个数,mod m为j的方案数
记得状态转移的时候,是乘法不是加法…我写成加法一直不过样例,快自闭了才发现这不是高中的组合原理,乘法嘛。。。我越来越废物了
可能比较难搞的是对于初始值的处理,也就是在这个范围内对于单个的数mod m为多少,分别由几个数
我是直接处理l,r之间有多少个数,看是否为m的倍数,不是的话就逐渐l++
记得限制一下l<=r,然后记录下每次l++时,对应mod出来的值来计数,
while((r-l+1)%m!=0&&l<=r){
dp[1][l%m]++;
l++;
}
因为m不大,O1时间就可以使得距离为m的倍数了,之后计算距离是m的多少倍,之后每个mod出来数都加上这个倍数就可以了
然后就是DP
for(int a=0;a<=m-1;a++){
for(int b=0;b<=m-1;b++){
dp[i][(a+b)%m]=max(dp[i][(a+b)%m],dp[i-1][a]*dp[1][b]);
}
}
总的代码为
ll dp[maxn][15];
void solve(){
ms(dp,0);
ll n=read(),m=read(),l=read(),r=read();
while((r-l+1)%m!=0&&l<=r){
dp[1][l%m]++;
l++;
}
if(l<=r){
ll dis=(r-l+1)/m;
for(int i=0;i<=m-1;i++){
dp[1][i]+=dis%mod;
dp[1][i]%=mod;
}
}
for(int i=2;i<=n;i++){
for(int a=0;a<=m-1;a++){
for(int b=0;b<=m-1;b++){
dp[i][(a+b)%m]+=(dp[i-1][a]*dp[1][b])%mod;
dp[i][(a+b)%m]%=mod;
}
}
}
cout<<dp[n][0]<<endl;
}
I
首先这是一个最短路问题,不过区别在于,每一次都可以修改一些路,使得这些路的权值增加365,求问每次修改后最短路径为多少,不可到达的话输出-1(每次修改,互相独立,不互相影响
首先修改后的路不走了肯定不行,因为有可能加上365它还是最短路的组成之一,求出最短路的路径,对应的路被修改了加上365也是不对的,因为这样就不一定是最短的了,
我们可以单独存下每两个点之间的附加权值(暂且这么取名
没被修改的为0,修改的为365,每次修改都需要清空前面的操作即清0
然后松弛的时候,把这两个点加上附加权值,被修改了就是加365,没被修改就是加0,没啥影响
想好了就开始写了,因为STL的map导致我很多次T。。所以我想了想就决定开个数组来记录附加权值,然后mle了…
改成map就过了…
我吐了
我写map,给我T,改成数组就能过
我写数组,给我mle,改成map就能过…
:)
int u,v,w,num;
bool flag=true;
int head[maxn],vis[maxn],dis[maxn];
struct node{
int fr,to,cost;
int nx;
}edge[maxn<<1];
map<pii,int>mp;
struct cmp{
bool operator () (const int a,const int b){
return dis[a]>dis[b];
}
};
void add(int a,int b,int c){
++num;
edge[num].fr=a;
edge[num].to=b;
edge[num].cost=c;
edge[num].nx=head[a];
head[a]=num;
}
void dijstra(int st){
ms(vis,0),ms(dis,inf);
dis[st]=0;
priority_queue<int,vector<int>,cmp>pq;
pq.push(st);
while(!pq.empty()){
int now=pq.top();
pq.pop();
if(vis[now]){
continue;
}
vis[now]=1;
for(int i=head[now];i!=-1;i=edge[i].nx){
int to=edge[i].to;
int cost=edge[i].cost;
cost+=mp[MP(now,to)];
if(!vis[to]&&dis[to]>dis[now]+cost){
dis[to]=dis[now]+cost;
pq.push(to);
}
}
}
}
void solve(){
num=0,ms(head,-1);
int n=read(),m=read(),q=read();
while(m--){
int a=read(),b=read(),c=read();
add(a,b,c);
add(b,a,c);
}
while(q--){
mp.clear();
int k=read();while(k--){
int p1=read(),p2=read();
mp[MP(p1,p2)]=mp[MP(p2,p1)]=365;
}
if(flag){
dijstra(1);
int ans=dis[n];
if(ans==inf){
flag=false;
puts("-1");
}else{
printf("%d\n",ans);
}
}else{
puts("-1");
}
}
}
J先略过…不仅因为是最难的防AK,同时还因为这是一个大模拟
大模拟是不可能写的,这辈子都不可能去写大模拟的 :)
上次写的那个象棋大模拟,把我整到现在还有阴影…
K
这个题…我觉得不难…hhw说是一个分块啥的…
(我刚开始还疑惑,怎么出了俩线段树…
但是很明显可以用线段树过去啊
区间最值,区间修改
这不很明显是线段树的操作吗?
相对而言,我觉得这还是线段树里面较为简单的,首先建树就不用说了,初始的最值就是它自己
然后修改,由于是修改成r-L+1,和普通的修改不一样,但仔细想想,同样是lazy标记,我们只需要记录下每次修改的左端为lazy值,然后对于每个数修改成这个位置到L的距离就好了,区间最值很明显是这个区间最右边的数…
总体来说就是一个简单的线段树变式吧
struct node{
int l,r;
int val,tag;
}tree[maxn<<2];
void build(int rt,int l,int r){
tree[rt].l=l;
tree[rt].r=r;
tree[rt].tag=0;
if(l==r){
tree[rt].val=read();
}else{
build(lson);
build(rson);
tree[rt].val=max(tree[ls].val,tree[rs].val);
}
}
inline void push_down(ll rt){
if(!tree[rt].tag){
return ;
}
int l=tree[rt].l,r=tree[rt].r;
if(l==r){
return ;
}
tree[ls].tag=tree[rs].tag=tree[rt].tag;
int L=tree[rt].tag;
tree[ls].val=(tree[ls].r-L+1);
tree[rs].val=(tree[rs].r-L+1);
tree[rt].val=max(tree[ls].val,tree[rs].val);
tree[rt].tag=0;
}
void updata(int rt,int a,int b,int L){
//de(a),de(b),de(L);
ll l=tree[rt].l,r=tree[rt].r;
if(l==a&&r==b){
tree[rt].tag=L;
tree[rt].val=(r-L+1);
return ;
}else{
push_down(rt);
if(b<=md){
updata(ls,a,b,L);
}else if(a>=md+1){
updata(rs,a,b,L);
}else{
updata(ls,a,md,L);
updata(rs,md+1,b,L);
}
tree[rt].val=max(tree[ls].val,tree[rs].val);
}
}
int query(int rt,int a,int b){
int l=tree[rt].l,r=tree[rt].r;
if(l==a&&r==b){
//de(a),de(b),de(tree[rt].val);
return tree[rt].val;
}
push_down(rt);
if(b<=md){
return query(ls,a,b);
}else if(a>=md+1){
return query(rs,a,b);
}else{
return max(query(ls,a,md),query(rs,md+1,b));
}
}
void solve(){
int n=read(),m=read();
build(1,1,n);while(m--){
int ope=read(),a=read(),b=read();
if(ope==1){
updata(1,a,b,a);
}else{
cout<<query(1,a,b)<<endl;
}
}
}
L题,看出来是数位dp了,,,
但我一直没看懂数位dp咋写,也就没去学了…
晚点补了