2019年7月22日(数据结构与搜索)

考炸了!!!!!!

好xie啊,考成这个鬼样子,怕今年noip也是要爆萎了。

还没考完,来发总结了,今天考试真的不想说什么了……

还是认真写题解吧……

先按题目难度来排排,考试时只写出了problem2,有点那啥,那就先写这题的题解

prob2:集合(set)

题目大意:有一棵树,树上每个节点对应着一个集合,开始时都是空集,对每个节点从根到叶子进行一些操作(加入元素或删除元素),最后输出每个节点对应集合的大小

我的做法:树上操作,顺序又是从根到叶子,首先想到的就是\(dfs\)一波。然后对于集合的去重与删元素操作,蒟蒻想到的做法是建立一棵值域线段树,然后按\(dfs\)顺序进行该节点的操作,当遍历完这一个节点为根的子树后,将该节点的操作还原,这样将不会影响其他子树的答案输出。

貌似\(zsb\)大佬有只用\(dfs\)实现的方法,但思路貌似差不多,其他几道题炸了还要整理,就没看他的方法了

好吧,我也没想到这方法\(RE\)飞了。仔细想想,其实早应该想到的,即使是权值线段树,节点数也应该开\(4\)倍,第几次了!!!!!

ririririri……

汗,这下真的考飞了、

代码如下(改完的):

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 110000
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
struct line_tree{int l,r,sz,ans;}lt[xx<<2];
int num=0,res[xx],now;
vector<int>e[xx],ee[xx],eee[xx];
bool vis[xx];
inline void build(int k,int i,int j)
{
    lt[k].l=i;lt[k].r=j;
    if(i==j) return;
    int q=k<<1,mid=(i+j)>>1;
    build(q,i,mid);
    build(q+1,mid+1,j);
}
inline void up(int k)
{
    int q=k<<1;
    lt[k].ans=lt[q].ans+lt[q+1].ans;
}
inline void add(int k,int x,int c)
{
    if(lt[k].l==lt[k].r)
    {
        now=lt[k].sz;
        lt[k].sz+=c;
        lt[k].sz=max(lt[k].sz,0);
        lt[k].sz=min(lt[k].sz,1);
        lt[k].ans=lt[k].sz?1:0;
        return;
    }
    int q=k<<1,mid=lt[q].r;
    if(x<=mid) add(q,x,c);
    else add(q+1,x,c);
    up(k);
}
inline void change(int k,int x,int c)
{
    if(lt[k].l==lt[k].r)
    {
        lt[k].sz=c;
        lt[k].ans=lt[k].sz?1:0;
        return;
    }
    int q=k<<1,mid=lt[q].r;
    if(x<=mid) change(q,x,c);
    else change(q+1,x,c);
    up(k);
}
inline void dfs(int g)
{
    fur(i,0,(int)ee[g].size()-1)
    {
        if(ee[g][i]<0) add(1,-ee[g][i],-1);
        else add(1,ee[g][i],1);
        eee[g].push_back(now);
    }
    res[g]=lt[1].ans;
    fur(i,0,(int)e[g].size()-1) if(!vis[e[g][i]]) vis[e[g][i]]=true,dfs(e[g][i]);
    fur(i,0,(int)ee[g].size()-1)
    {
        if(ee[g][i]<0) change(1,-ee[g][i],eee[g][i]);
        else change(1,ee[g][i],eee[g][i]);
    }
}
int main()
{
    int n=in;
    fur(i,1,n-1)
    {
        int x=in,y=in;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    fur(i,1,n)
    {
        int k=in;
        fur(j,1,k)
        {
            int x=in;
            ee[i].push_back(x);
        }
    }
    build(1,1,n);
    vis[1]=true;
    dfs(1);
    fur(i,1,n) printf("%d\n",res[i]);
    return 0;
}

正解:思想如上,但数据结构由值域线段树变为桶,做到\(O(1)\)修改。

扫描二维码关注公众号,回复: 6970635 查看本文章
这下复杂度就对了,时间空间都舒服很多,而且不会有恶心的情况,代码如下:  
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 110000
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
int num=0,res[xx],before;
vector<int>e[xx],ee[xx],eee[xx];
bool vis[xx],cnt[xx];
inline void dfs(int g)
{
    fur(i,0,(int)ee[g].size()-1)
    {
        before=cnt[abs(ee[g][i])];
        if(ee[g][i]<0) cnt[-ee[g][i]]=false;
        else cnt[ee[g][i]]=true;
        num+=(cnt[abs(ee[g][i])]-before);
        eee[g].push_back(before);
    }
    res[g]=num;
    fur(i,0,(int)e[g].size()-1) if(!vis[e[g][i]]) vis[e[g][i]]=true,dfs(e[g][i]);
    fur(i,0,(int)ee[g].size()-1)
    {
        before=cnt[abs(ee[g][i])];
        if(ee[g][i]<0) cnt[-ee[g][i]]=eee[g][i];
        else cnt[ee[g][i]]=eee[g][i];
        num+=(cnt[abs(ee[g][i])]-before);
    }
}
int main()
{
    int n=in;
    fur(i,1,n-1)
    {
        int x=in,y=in;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    fur(i,1,n)
    {
        int k=in;
        fur(j,1,k)
        {
            int x=in;
            ee[i].push_back(x);
        }
    }
    vis[1]=true;
    dfs(1);
    fur(i,1,n) printf("%d\n",res[i]);
    return 0;
}

prob1:中位数(median)

题目大意:给出一个1~n的排列,询问每个含有奇数个元素的区间的中位数m与区间左右两端的乘积的和

40分部分分:这次部分分还是给的蛮足的,1k不到的码量足足有40分,这没什么好说,纯暴力,每次重排,找中位数。时间复杂度\(O(n^3logn)\)

暴力搞法:在\(xgzc\)大佬的帮助下改进了蒟蒻未成形的幻想,易知:共\(\dfrac{n^2}{2}\)个区间,统计中位数的话蒟蒻想整体二分一波求区间\(mid\)小值,但大佬说这样时间复杂度为\(O(n^2logn)\)。而且常数大,不好卡,所以是失败的。而用两个堆(一个大根,一个小根)用来维护中位数可以做到相同的复杂度,并且常数可以小到忽略不计。又由于本题可以开\(O2\)\(n\)值域较小(\(<=1e4\)),故可以如此暴力骚过。

好吧,也并没有过,被卡了,数据还是大了,学长太强大了 qwq

代码如下:

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define xx 11000
#define ll long long
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
priority_queue<ll>e;
priority_queue<ll,vector<ll>,greater<ll> >f;
ll a[xx],ans=0;
int main()
{
    int n=in;
    fur(i,1,n) a[i]=in;
    fur(i,1,n)
    {
        while(!e.empty()) e.pop();
        while(!f.empty()) f.pop();
        e.push(a[i]);
        ans+=i*i*e.top();
        for(int j=i+2;j<=n;j+=2)
        {
            int now=0;
            fur(k,j-1,j)
            {
                if(!now)
                {
                    if(f.empty()||a[k]<=f.top()) e.push(a[k]);
                    else
                    {
                        e.push(f.top());
                        f.pop();
                        f.push(a[k]);
                    }
                    now++;
                }
                else
                {
                    if(a[k]>=e.top()) f.push(a[k]);
                    else
                    {
                        f.push(e.top());
                        e.pop();
                        e.push(a[k]);
                    }
                }
            }
            ans+=i*j*e.top();
        }
    }
    printf("%lld\n",ans);
    return 0;
}

正解:开双向链表,可以做到将中位数\(O(1)\)地转移。思想与上一样,不多说了,说多了都是泪。上代码(用的学长的题解,看不懂,算了):

#include <iostream>
#include <vector>
using namespace std;
typedef long long int64;
int main() {
    freopen("median.in","r",stdin);
    freopen("median.out","w",stdout);
    int n; cin >> n;
    vector<int> element(n);
    for (int i = 0; i < n; i += 1) {
        cin >> element[i];
    }
    vector<pair<int, int>> neighbours(n + 1);
    int64 result = 0;
    for (int i = 0; i < n; i += 1) {
         //如果索引在[i,n]范围内,则该值有效 
        vector<bool> valid(n + 1, false);
        for (int j = i; j < n; j += 1) {
            valid[element[j]] = true;
        }
        valid[n + 1] = true;
        int last = 0;
        //值的数目>中间值,较低值的数目<=中间值
        //mid->中间值
        int bigger = (n - i), lower = 0, mid = 0;
        //  计算每个值,左边和右边的下一个有效值 
        for (int j = 1; j <= n; j += 1) {
            if (not valid[j]) {
                continue;
            }
            if (bigger > lower) {
                mid = j;
                bigger -= 1;
                lower += 1;
            }
            // 最后一个元素将是当前元素左边的元素
            // 最后一个元素右边的元素将是这个元素
            neighbours[j].first = last;
            neighbours[last].second = j;
            last = j;
        }
        for (int j = n -1 ; j >= i; j -= 1) {
            if (lower == bigger + 1) {
                // this can be any formula, whatsoever
                result += (int64) (j + 1) * (i + 1) * mid;
            } 
            // 值位于擦除值的左侧和右侧
            int left = neighbours[element[j]].first;
            int right = neighbours[element[j]].second;
            // 从“likef列表”中删除此列表
            neighbours[left].second = right;
            neighbours[right].first = left;
            // 更新中间值、较大或较小的值。
            if (element[j] == mid) {
                bigger -= 1;
                mid = right;
            } else if (element[j] < mid) {
                lower -= 1;
            } else {
                bigger -= 1;
            }
            while (lower > bigger) {
                mid = neighbours[mid].first;
                bigger += 1;
                lower -= 1;
            }

            // move the median to the right
            while (bigger > lower) {
                mid = neighbours[mid].second;
                bigger -= 1;
                lower += 1;
            }
        }
    }
    cout << result << '\n';
    return 0;
}

其他解法:请容许我向大家隆重介绍\(yyz\)大佬的解法,简直与美丽的莫队有着异曲同工之妙,同样是通过\(O(1)\)地删、增值来转移\(mid\)指向的位置,实现了与正解同样优秀的\(O(n^2)\)算法,爆踩\(std\)\(orz\)……

详情见代码:

#include<iostream>
#include<cstdio>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;++i)
#define int long long
#define xx 10010
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
int cnt[xx],a[xx];
signed main()
{
    int n=in;
    fur(i,1,n) a[i]=in;
    int l=1,r=0,mid=0,cnnt=0,ans=0;
    fur(tl,1,n)
    {
        for(int tr=tl;tr<=n;tr+=2)
        {
            while(l<tl)
            {
                if(a[l]<=mid) --cnnt;
                --cnt[a[l++]];
            }
            while(r<tr)
            {
                ++r;
                if(a[r]<=mid) ++cnnt;
                ++cnt[a[r]];
            }
            while(r>tr)
            {
                if(a[r]<=mid) --cnnt;
                --cnt[a[r--]];
            }
            int need=((tr-tl)>>1)+1;
            if(need==1)
            {
                mid=a[tl];
                cnnt=1;
                ans+=tr*tl*mid;
                continue;
            }
            while(cnnt<need) cnnt+=cnt[++mid];
            while(cnnt>need)
            {
                if(cnnt-cnt[mid]<need) break;
                cnnt-=cnt[mid--];
            }
            while(cnt[mid]==0) --mid;
            ans+=tl*tr*mid;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

prob4:星空(stars)

题目大意:给定平面上一些点对,每个点都有其对应权值,求给定大小的矩阵最大点权和。

扫描线裸题(可惜我不会

还是先给40分暴力做法吧:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define in read()
#define fur(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define xx 55000
inline int read()
{
    int x=0,f=1;char ch=getchar();
    for(;!isalnum(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
struct point{int x,y,z;}dot[xx];
int ans=0;
inline bool cmp(point a,point b){return (a.x^b.x)?(a.x<b.x):(a.y<b.y);}
int main()
{
    int n=in,w=in,h=in;
    fur(i,1,n)
    {
        dot[i].x=in;
        dot[i].y=in;
        dot[i].z=in;
    }
    sort(dot+1,dot+n+1,cmp);
    fur(i,1,n)
    {
        int j=i+1,tmp=dot[i].z;
        while(j<=n&&dot[j].x-dot[i].x<=w)
        {
            if(dot[j].y-dot[i].y<=h&&dot[j].y>=dot[i].y) tmp+=dot[j].z;
            j++;
        }
        int j1=i+1,tmp1=dot[i].z;
        while(j1<=n&&dot[j1].x-dot[i].x<=w)
        {
            if(dot[i].y-dot[j1].y<=h&&dot[j1].y<=dot[i].y) tmp1+=dot[j1].z;
            j1++;
        }
        ans=max(ans,max(tmp,tmp1));
    }
    printf("%d\n",ans);
    return 0;
}

好了,水程序就用来水字数了,接下来还是要进入正题

正解:扫描线加线段树优化,详情见代码:

#include <cstdio>
#include<cstring>
#include <iostream>
#include <algorithm>
#define ls (x<<1)
#define rs (x<<1|1)
using namespace std;
typedef long long ll;
ll geti(){
    char ch=getchar(),k=1;ll ret=0;
    while((ch<'0' || ch>'9') && ch!='0')ch=getchar();
    if(ch=='-')k=0,ch=getchar();
    while(ch>='0' && ch<='9')ret=ret*10+ch-'0',ch=getchar();
    return k?ret:-ret;
}
const int maxn = 100000 + 1000;
struct XDS{
    ll mx[maxn*8],lazy[maxn*8];
    void pushdown(int x){
        mx[ls]+=lazy[x];mx[rs]+=lazy[x];
        lazy[ls]+=lazy[x];lazy[rs]+=lazy[x];
        lazy[x]=0;
    }
    void update(int x,int l,int r,int xl,int xr,ll v){
        if(xl==l && r==xr){
            mx[x]+=v;lazy[x]+=v;return;
        }
        int mid=(l+r)>>1;
        if(xr<=mid)update(ls,l,mid,xl,xr,v);
        else if(xl>mid)update(rs,mid+1,r,xl,xr,v);
        else update(ls,l,mid,xl,mid,v),update(rs,mid+1,r,mid+1,xr,v);
        mx[x]=max(mx[ls],mx[rs])+lazy[x];
    }
    ll query(int x,int l,int r,int xl,int xr){
        if(l==xl && r==xr){
            return mx[x];
        }
        int mid=(l+r)>>1;
        if(xr<=mid)return query(ls,l,mid,xl,xr)+lazy[x];
        else if(xl>mid)return query(rs,mid+1,r,xl,xr)+lazy[x];
        else return max(query(ls,l,mid,xl,mid),query(rs,mid+1,r,mid+1,xr))+lazy[x];
    }
}t;
int n,w,h;
struct star{
    ll x,y,ty,l;
}a[maxn];
ll hs[maxn];
bool cmpx(const star &x,const star &y){return x.x<y.x;}
bool cmpy(const star &x,const star &y){return x.ty<y.ty;}
int main()
{
        scanf("%d%d%d",&n,&w,&h)
        for(int i=1;i<=n;i++)
        {
            a[i*2-1].x=geti();a[i*2-1].ty=geti();a[i*2-1].l=geti();
            a[i*2].x=a[i*2-1].x+w+1;a[i*2].ty=a[i*2-1].ty;a[i*2].l=-a[i*2-1].l;
        }
        n=n*2;
        sort(a+1,a+1+n,cmpy);
        int s=0;
        for(int i=1;i<=n;i++)
        {
            if(i!=1 && a[i].ty==a[i-1].ty) a[i].y=s;
            else
            {
                if(a[i].ty-hs[s]>1) ++s,hs[s]=hs[s-1]+1;
                ++s;hs[s]=a[i].ty;
                a[i].y=s;
            }
        }
        hs[++s]=a[n].ty+1;
        sort(a+1,a+1+n,cmpx);
        ll ans=0;
        for(int i=1;i<=n;i++)
        {
            int l=a[i].y,r=s,mid,rt=-1;
            ll cur=a[i].ty+h;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(hs[mid]<=cur) rt=mid,l=mid+1;
                else r=mid-1;
            }
            t.update(1,1,s,a[i].y,rt,a[i].l);
            if(a[i].x!=a[i+1].x || i==n) ans=max(ans,t.query(1,1,s,1,s));
        }
        printf("%lld\n",ans);
        return 0;   
}

prob3:星际争霸(craft)

题目大意:给定一个\(c*r\)的矩阵,其中的格子有可通过与不可通过两种,给定主人公与两个敌人的位置与二者血量,求是否可以赢得游戏与赢得游戏的最小回合数

\(MDzhizhang\),学长说这题是道\(sb\)爆搜,可以打爆搜切题,不想说什么了。好像没什么时间了,最后一个直接\(kuai\)题解的算了(标程用的\(dp\)):

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
LL gi () {
    LL ret=0; char ch=getchar();
    while((ch<'0' || ch>'9') && ch!='-') ch=getchar();
    char c=ch=='-'?getchar():ch;
    while(c>='0' && c<='9') ret=ret*10+c-'0',c=getchar();
    return ch=='-'?-ret:ret;
}
int n,m,tl,pos[37][37],w[37][37],near[37][37],h_m,h_z,dx[4]={0,-1,0,1},dy[4]={-1,0,1,0};
int dp[55][37][37][37][18];
char mp[7][7];
int go (int p,int d) {
    int x = (p-1)/m, y = (p-1)%m;
    int nx = x + dx[d], ny = y + dy[d];
    if(nx < 0 || nx > n || ny < 0 || ny > m || mp[nx][ny]=='1') return -1;
    return pos[nx][ny];
}
int zgo (int p,int g) {
    if(near[p][g] || !p) return p;
    int res = -1, dis = 1e9;
    for(int i=0;i<=3;i+=1) {
        int c = go(p,i);
        if(c==-1) continue;
        if(w[c][g] < dis) dis = w[c][g], res = c;
    }
    if(dis > 100) return p;
    return res;
}
void upd (int &x, int y) {
    x = x < y ? x : y;
}
int main (int argc, char* argv[]) {
    freopen("craft.in","r",stdin);
    freopen("craft.out","w",stdout);
    n = gi(), m = gi(), tl = gi();
    for(int i=0;i<n;i+=1) scanf("%s",mp[i]);
    h_m = gi(); h_z = gi();
    int p1 = -1, p2 = -1, p3 = -1;
    for(int i=0;i<n;i+=1) {
        for(int j=0;j<m;j+=1) {
            pos[i][j] = i*m+j+1;
            if(mp[i][j]=='z' || mp[i][j]=='Z') {
                if(p2 == -1) p2 = pos[i][j]; else p3 = pos[i][j];
            }
            else if(mp[i][j]=='M') p1 = pos[i][j];
        }
    }
    memset(w,60,sizeof w);
    for(int i=0;i<n;i+=1) {
        for(int j=0;j<m;j+=1) {
            if(mp[i][j] == '1') continue;
            if(i && mp[i-1][j] != '1') w[pos[i][j]][pos[i-1][j]] = 1;
            if(i!=n-1 && mp[i+1][j] != '1') w[pos[i][j]][pos[i+1][j]] = 1;
            if(j && mp[i][j-1] != '1') w[pos[i][j]][pos[i][j-1]] = 1;
            if(j!=m-1 && mp[i][j+1] != '1') w[pos[i][j]][pos[i][j+1]] = 1;
            w[pos[i][j]][pos[i][j]] = 0;
        }
    }
    int sz = n*m;
    for(int i=1;i<=sz;i+=1) {
        for(int d=0;d<=3;d+=1)
            if(go(i,d) != -1) near[i][go(i,d)] = 1;
    }
    for(int k=1;k<=sz;k+=1)
        for(int i=1;i<=sz;i+=1)
            for(int j=1;j<=sz;j+=1) w[i][j] = min(w[i][j], w[i][k] + w[k][j]);
    memset(dp,60,sizeof dp);
    dp[0][p1][p2][p3][h_m] = h_z * 2;
    dp[0][p1][p3][p2][h_m] = h_z * 2;
    for(int i=0;i<tl;i+=1)
        for(int t1=1;t1<=sz;t1+=1)
            for(int t2=0;t2<=sz;t2+=1)
                for(int t3=0;t3<=sz;t3+=1)
                    for(int h=1;h<=h_m;h+=1) {
                        int cur = dp[i][t1][t2][t3][h];
                        if(cur > 100) continue;
                        // 1. move
                        for(int d=0;d<=3;d+=1) {
                            int n1 = go(t1,d);
                            if(n1 == -1 || n1 == t2 || n1 == t3) continue;
                            int v = 0;
                            int n2 = zgo(t2,n1), n3 = zgo(t3,n1);
                            if(near[t2][n1]) ++v;
                            if(near[t3][n1] && t2 != t3 && cur > h_z) ++v;
                            if(h-v > 0) {
                                upd(dp[i+1][n1][n2][n3][h-v], cur);
                            }
                        }
                        // 2. attack
                        int n2 = zgo(t2,t1), n3 = zgo(t3,t1);
                        if(cur == 1) {
                            printf("WIN\n%d",i+1); exit(0);
                        }
                        int v = 0;
                        if(near[t2][t1]) ++v;
                        if(near[t3][t1] && t2 != t3 && cur-1 > h_z) ++v;
                        if(h-v > 0) {
                            if(cur-1 > h_z) {
                                upd(dp[i+1][t1][n2][n3][h-v], cur-1);
                            }
                            else {
                                upd(dp[i+1][t1][n2][0][h-v], cur-1);
                            }
                        }
                    }
    puts("LOSE");
    return 0;
}

希望明天图论别水吧

猜你喜欢

转载自www.cnblogs.com/ALANALLEN21LOVE28/p/11312970.html