UESTC 2018 Summer Training #13 Div.2 初步题解

只做出来4题 h h h hhh hhh
太菜了还是。
一共10题,目前完成了 8 8 8道题,还有 A A A G G G未完成。
比赛地址


B B B:
要求:求有多少严格上升子序列。
题解:容易想到以第 i i i个元素为结尾的子序列个数等于所有之前的比它小的序列数加上它自己。
d p [ i ] = ∑ a [ j &lt; i ] &lt; a [ i ] d p [ j ] dp[i]=\sum_{a[j&lt;i]&lt;a[i]}dp[j] dp[i]=a[j<i]<a[i]dp[j]
因为序列长度不规定,所以 i i i用了第 j j j个做上一个元素, i + 1 i+1 i+1依然可以,所以公式是正确的。
那么如何快速求的右式呢。
联系道求逆序对时候,对线段树的做法:以权值来进行赋值。
我们这里以每个元素的值作为坐标进行赋值,赋的值则是答案,这样子取前缀和可以直接判断出比自己小的权值和。
前缀和:用树状数组求,模板都有点忘了怎么写的了,中间有个 n n n和这边 n n n重合了导致样例过不去,真的是太菜了。因为两个 n n n在此时不是一个 n n n

C C C:
要求:两个人在矩形内画圆,判断谁最后没的画,另一个人就获胜。
题解:第一个人画非对称,第二个人肯定画对称。第一个不能画对称的圆形,因为如果这样,后面的人也画对称一定是第一个先没的画。而画非对称能够保证第二个人面对从零开始的对称图,那么第二个人必败。
判断是不是能画出第一个圆即可。

D D D:
要求: n n n个人, m m m个座位,这些人被分成 10 10 10种人,每个人都想要自己种数的倍数的座位( 1 、 2 、 3... 1、2、3... 123...),求最多能满足多少人。
题解:
座位可以很多,所以暴力是不行的。
我们考虑如何快速计算处理出 1 、 2 、 3... 1、2、3... 123...的倍数。求出其公倍数: 2520 2520 2520,也就是说 2520 2520 2520是所有 1...10 1...10 1...10的倍数,所有的数都可以转换成 m = k ∗ 2520 + p m=k*2520+p m=k2520+p的形式。如果 m m m可以被 i i i整除,那么 p p p一定可以被 i i i整除,二者是等价的,因为 2520 ∗ k 2520*k 2520k不会有任何影响,它可以除尽所有。
每个 i i i有一定的数量,每个 i i i对应几个 p p p p p p也有一定的数量,求 i i i可以实现的数量。
考虑网络流。
处理 p p p的数量的时候比较复杂,我们首先处理出有多少种情况(是 1 、 2 、 5 1、2、5 125倍数的是一种情况),对应每个情况我们存好个数,遍历一遍 ∗ k *k k,如果余数在范围内那么就 + + ++ ++.
跑最大流即是答案。

void init(){
    memset(mark,0,sizeof(mark));
    memset(vis,0,sizeof(vis));
    FOR(i,1,2520){
        int x=0;
        FOR(j,1,10)if(i%j==0)x|=1<<(j-1);
        if(!vis[x])vis[x]=++cnt,p[vis[x]]=x;
        mark[i]=vis[x];//存的是第几个,是同时是1、3、5倍数的等等。
    }
}

int main(){
    cin>>T;
    init();
    while(T--){
        memset(have,0,sizeof(have));
        scanf("%d",&n);
        FOR(i,1,10)scanf("%d",&num[i]);
        for(int i=1;i<=2520;i++){
            int x=n/2520;
            have[mark[i]]+=x;
            if(i+x*2520<=n)have[mark[i]]++;
        }
        dc.init(cnt+12,cnt+11,cnt+12);
        for(int i=1;i<=10;i++){
            dc.AddEdge(cnt+11,i,num[i]);
        }
        for(int i=1;i<=cnt;i++){
            dc.AddEdge(10+i,cnt+12,have[i]);
        }
        for(int i=1;i<=cnt;i++){
            for(int j=1;j<=10;j++){
                int x=1<<(j-1);
                if(x&p[i])dc.AddEdge(j,10+i,inf);
            }
        }
        printf("%lld\n",dc.Maxflow());
    }
}

E E E:
要求:恶心的模拟题,英语不好一直读错题意。有 10 10 10台机器,你要按顺序训练 3 3 3遍,每次有训练时间和恢复时间。同时每台机器还有一个人也会有相同的训练,不过他在 T i T_i Ti才会到达,然后反复训练恢复。如果你们同时到达,你会让给他先。

题解:模拟即可,每次判断到没到,如果没到,直接训练。这里我们将 T i T_i Ti延申为准备好了的时间。所以我们训练完的时间和 T i T_i Ti取最大值,因为可能他休息好了也要等我们训练完。
从头开始:
没到,训练,更新 T i T_i Ti
到了,判断此时处于哪个阶段,直接用 t − T i t-T_i tTi除以他的一次的总时间,计算出:如果在训练阶段,直接令 t t t等于训练结束的时间,并且 T i T_i Ti更新到休息时间,然后 t t t加上自己的训练时间,并更新 T i T_i Ti,因为现在可能超过了他的开始训练时间了,他在等我们,也有可能是没超。
反复判断即可。记得减去最后一次的休息时间。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define ull unsigned long long
using namespace std;

ll A[15][2],B[15][2];
ll T[12];

int main(){
    FOR(i,1,10)scanf("%lld%lld",&A[i][0],&A[i][1]);
    FOR(i,1,10){
        scanf("%lld%lld%lld",&B[i][0],&B[i][1],&T[i]);
    }
    ll t=0;
    FOR(j,1,30){
        int i = j%10==0?10:j%10;
        if(t>=T[i]){
            ll tmp=(t-T[i])/(B[i][0]+B[i][1])*(B[i][0]+B[i][1]);
            T[i]+=tmp;
            if(t<T[i]+B[i][0]){
                t=T[i]+B[i][0];
                T[i]=t+B[i][1];
            }
            else T[i]+=B[i][0]+B[i][1];
        }
        t+=A[i][0];
        T[i]=max(T[i],t);
        t+=A[i][1];
//        cout<<t<<endl;
    }
    t-=A[10][1];
    cout<<t<<endl;
}

F : F: F:
要求:求连续长度为 m m m的区间,从左到右不断更新最大值,并且记录区间内更新次数。最后计算所以 m m m长的区间贡献和。
题解:差不多是单调队列板题了,只不过需要从反向进行,维护一个单调增的队列(最大值更新的过程)。如果遇到更大的,说明后面的小不会出现,直接弹出,最后记录即可。典型的正难则反。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int T;
int n,m,k;
ll p,q,r,mod;
vector<int>G;
deque<pair<int,int> >dq;

int main(){
    cin>>T;
    while(T--){
        dq.clear();
        scanf("%d%d%d%d%d%d%d",&n,&m,&k,&p,&q,&r,&mod);
        G.clear();
        for(int i=1;i<=k;i++){
            int x;scanf("%d",&x);
            G.push_back(x);
        }
        for(int i=k+1;i<=n;i++){
            ll tmp=(p*(ll)G[G.size()-1]+q*(ll)i+r)%mod;
            G.push_back((int)tmp);
        }
        ll suma=0,sumb=0;
        for(int i=n-1;i>n-m-1;i--){
            while(!dq.empty()&&G[i]>=dq.back().first)dq.pop_back();
            dq.push_back(make_pair(G[i],i));
        }
       // cout<<dq.size()<<endl;
        suma+=dq.front().first^(n-m+1);sumb+=dq.size()^(n-m+1);
        for(int i=n-1-m;i>=0;i--){
            while(!dq.empty()&&dq.front().second>i+m-1)dq.pop_front();
            while(!dq.empty()&&G[i]>=dq.back().first)dq.pop_back();
            dq.push_back(make_pair(G[i],i));
            suma+=dq.front().first^(i+1);
            sumb+=dq.size()^(i+1);
            //cout<<dq.front()<<" "<<dq.back()<<endl;
        //cout<<dq.size()<<endl;
        }
        printf("%lld %lld\n",suma,sumb);
    }

}

H : H: H:很简单的,按奇数偶数判断走哪一步。

I I I: f l o y d floyd floyd板题,事先预处理黑洞距离为 0 0 0

J J J:
题意:对于相同长的两个列表,求出最多集合数,每个连续的集合内元素相等。
题解:我的做法:类似于求最少交换次数实现数组有序的方法,但是特判每次处理到的最大点是否和已经处理完的最大点相等(否则就是跳跃的),这样是 T L E TLE TLE的,继续优化,每次不要的可是直接删掉,用链表 A C AC AC
正解:每次按顺序从上到下,从左到右,放入集合中,如果存在就删去,走完一个右判断 s e t set set是否为 0 0 0
我的做法也太麻烦了。膜一发。明天要开始搜索的学习和训练了, e m m m emmm emmm下一次训练可能放在下周五?可能 m a y b e maybe maybe

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define ull unsigned long long
using namespace std;
ull base = 131;
int T;

map<ull,int>M;
char tmp[10];
int swit[1000050],flag[1000050];
int lst[1000050],rst[1000050];

void del(int x){
    lst[rst[x]]=lst[x];
    rst[lst[x]]=rst[x];
}

ull Hash(char ch[]){
    unsigned long long ans=0;
    int len=strlen(ch);
    for(int i=0;i<len;i++)
        ans=ans*base+ch[i];
    return ans;
}

int main(){
    cin>>T;
    while(T--){
        int n;cin>>n;
        M.clear();
        memset(flag,0,sizeof(flag));
        for(int i=1;i<=n;i++){
            scanf("%s",tmp);
            M[Hash(tmp)]=i;
            lst[i]=i-1;
            rst[i]=i+1;
        }
        for(int i=1;i<=n;i++){
            scanf("%s",tmp);
            swit[i]=M[Hash(tmp)];
        }
        int x,cnt=0,mx=-1,num=0;
        for(int i=1;i<=n;){
            x=i;
            do{
                num++;
                mx=max(mx,x);
                flag[x]=1;
                x=swit[x];
                cnt++;
                del(x);
            }while(!flag[x]);
            if(mx==num){
                printf("%d ",cnt);
                cnt=0;
            }
            i=rst[i];
            //cout<<i<<endl;
        }
        printf("\n");
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值