wust周赛(A~L)不含(E,F,J,L)(待补

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咋写,也就没去学了…
晚点补了

猜你喜欢

转载自blog.csdn.net/leoxe/article/details/105895108
l