zoukankan      html  css  js  c++  java
  • 开学考试题5:2017黑龙江省选

    Day1

    T1:P3745 [六省联考2017]期末考试

    隐藏的水题。。。

    分析:

    一看题,难,再看数据范围,连dfs爆搜的分都没有。但其中很多特殊点暗示了做法:

    1. A B大,C小,说明只能让学生不愉快,不能调课。

    2.C大,A B小,说明只能用AB两种方式调课:那么我们为了不让学生产生不愉快度,就要满足b[i]中最大的<=t[i]中最小的(记为minn)。

    若B>A,直接把超时的科目都提前,统计代价。

    若A<=B,优选A方式,可是A方式要满足将天数提前的同时,也要将部分科目天数延后,所以考虑计算<minn的b值可以延后的总天数是多少,>minn值需要提前的总天数是多少,

    然后比较:够了就都用A,不够的部分用B的代价来补。

    3.……

    2.的做法暗示了当已知最晚科目时间下的做法,可是我们并不知道最晚科目的时间。怎么办?     ——枚举就好了。

    按这种思路,可以产生一个n^2的做法了。

    考虑优化:

    1. 枚举一个最晚时间,统计使科目调整到那个时间的最小花费,以及学生产生的不愉快度时,我们是O(n)去做的,将t,b排序,用两个前缀和数组,维护一下,统计答案时直接二分找到不满足的位置,然后用前缀和O(1)统计即可,时间复杂度:O(n*logn)

    2.发现答案是满足先递减再递增的性质的,可以用三分。O(n*logn)

    3.初始化几个数组(真的不怎么懂他们是怎么做的),然后各种搞。。 O(n)。

    我打的是第一种。

    总结:

    通过特殊点推测正解,发现问题的本质。

    代码细节要注意,该开long long就要开,register int是int类型!!

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ri register int
    #define N 100005
    const ll inf=1ll<<62;
    ll t[N],b[N],A,B,C,n,m,maxx=0,minn=inf,sumt[N],sumb[N];
    ll read()
    {
        ll x=0; int fl=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
        return x*fl;
    }
    void work1()
    {
        ll maxn=0,ans=0;
        for(ri i=1;i<=m;++i) maxn=max(maxn,b[i]);
        for(ri i=1;i<=n;++i) if(t[i]<maxn) ans+=(maxn-t[i])*C;
        printf("%lld
    ",ans);
    }
    ll query1(ll x)//zeng B
    {
        ll pos=lower_bound(b+1,b+m+1,x)-b;
        ll tmp=( sumb[m]-sumb[pos-1] - x*(m-pos+1) )*B;
        return tmp;
    }
    ll query2(ll x)//change A
    {
        ll sum1=0,sum2=0;
        ll pos=lower_bound(b+1,b+1+m,x)-b;
        sum1=x*(pos-1) - sumb[pos-1]; 
        sum2=sumb[m]-sumb[pos-1] - x*(m-pos+1);
        if(sum1>=sum2) return sum2*A;
        else return sum1*A+(sum2-sum1)*B;
    }
    void work2()
    {
        ll ans=inf;
        for(ri i=1;i<=maxx;++i){
            ll tmp=0;
            if(A>=B) tmp=query1(i);
            else tmp=query2(i);
            ll pos=lower_bound(t+1,t+1+n,i)-t;
            tmp+=( (ll)i*(pos-1) - sumt[pos-1] )*C;
            if(tmp>=0) ans=min(ans,tmp);
        }
        printf("%lld
    ",ans);
    }
    void work3()
    {
        ll ans=0;
        if(A>=B) ans=query1(minn);
        else ans=query2(minn);
        printf("%lld
    ",ans);
    }
    int main()
    {
        freopen("exam.in","r",stdin);
        freopen("exam.out","w",stdout);
        A=read(); B=read(); C=read();
        n=read(); m=read();
        for(ri i=1;i<=n;++i) t[i]=read(),maxx=max(maxx,t[i]),minn=min(minn,t[i]);
        for(ri i=1;i<=m;++i) b[i]=read(),maxx=max(maxx,b[i]);
        sort(t+1,t+1+n); sort(b+1,b+1+m);
        for(ri i=1;i<=n;++i) sumt[i]=sumt[i-1]+t[i];
        for(ri i=1;i<=m;++i) sumb[i]=sumb[i-1]+b[i];
        if(A==B && B==1e9 && C<A) work1();
        else if(C==1e16) work3();
        else work2();    
    }
    /*
    3 5 4
    5 6
    1 1 4 7 8
    2 3 3 1 8 2
    
    1000 1000 10000000000000000
    2 2
    1 3
    2 3
    
    149 896 130
    2 5
    1 4
    2 6 8 8 9
    */
    View Code

    T2:P3747 [六省联考2017]相逢是问候

    首先要明确一点:指数不能随便取模!!

    可以先做一下这道题:P4139 上帝与集合的正确用法

    所以欧拉定理就派上用场了:欧拉定理推论(其实是别人写的特别好的题解)

    那么怎么求一个数的欧拉值呢?

    1.利用欧拉函数是积性函数的性质,线性筛递推求:O(n)

    void init_phi()
    {
        for(ri i=2;i<=n;++i){
            if(!pri[i]) su[++cnt]=i,phi[i]=i-1;
            for(ri j=1;j<=cnt&&su[j]*i<=n;++j){
                pri[su[j]*i]=1;
                if(i%su[j]==0){
                    phi[su[j]*i]=su[j]*phi[i];
                    break;
                }
                else phi[su[j]*i]=phi[i]*(su[j]-1);
            }
        }
    }
    线性筛

    2.直接质因数分解求单个欧拉值:(注意特判素数)复杂度O(logn)

    ll calc_phi(ll x)
    {
        ll tmp=x;
        for(ri i=2;i<=sqrt(x);++i)
        if(x%i==0){
            tmp=tmp/i*(i-1);//利用公式求欧拉函数 
            while(x%i==0) x/=i;
        }
        if(x>1) tmp=tmp/x*(x-1);//处理质数情况 
        return tmp;
    }
    质因数分解求欧拉

    然后区间修改时,用线段树暴力递归到叶子节点去修改每一个值,怎么保证复杂度呢:每个数最多只会被修改log次,用一个tim数组记录被修改了多少次,超过一定次数后就返回。

    修改的时候利用上述公式递归求应该修改的值。

    但是时间复杂度太大了,就要初始化c的次幂,省去log c的复杂度。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ri register int
    #define mid ((l+r)>>1)
    #define N 50005
    ll sum[N*4],tim[N*4],c,a[N],phi[N],pow1[10005][30],pow2[10005][30],p,fl=0,cnt=0;
    bool b1[10005][30],b2[10005][30];
    int n,m;
    int read()
    {
        int x=0,fl=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
        return x*fl;
    }
    ll calc_phi(ll x)
    {
        ll tmp=x;
        for(ri i=2;i<=sqrt(x);++i)
        if(x%i==0){
            tmp=tmp/i*(i-1);//利用公式求欧拉函数 
            while(x%i==0) x/=i;
        }
        if(x>1) tmp=tmp/x*(x-1);//处理质数情况 
        return tmp;
    }
    void init()
    {
        ll tmp=p; phi[0]=p;//第0层的模数为它自己 
        while(tmp!=1) tmp=calc_phi(tmp),phi[++cnt]=tmp;//O(log p)求出p递归的欧拉值 
        phi[++cnt]=1; 
        for(ri i=0;i<=cnt;++i){
            pow1[0][i]=1;//pow1是c的 j次方   i是递归的层数 
            for(ri j=1;j<=10000;++j){
                pow1[j][i]=pow1[j-1][i]*c;
                if(pow1[j][i]>=phi[i]) pow1[j][i]%=phi[i],b1[j][i]=1;//b1标记处理指数大于phi[i]的情况 
                b1[j][i]|=b1[j-1][i];
            }
        }
        for(ri i=0;i<=cnt;++i){
            pow2[0][i]=1;//pow2是c的 j*10000 次方 
            b2[1][i]=b1[10000][i];
            for(ri j=1;j<=10000;++j){
                pow2[j][i]=pow2[j-1][i]*pow1[10000][i];
                if(pow2[j][i]>=phi[i]) pow2[j][i]%=phi[i],b2[j][i]=1;
                b2[j][i]|=b2[j-1][i]|b1[10000][i];//
            }
        }
    }
    ll calc(ll x,ll dep)//在第dep层时c的x次方 
    {
        fl=0;//便于dfs里面的标记判断 
        //以10000作为标准预处理出了c^i与c^(j*10000)的值 避免使用快速幂超时 
        ll x1=x/10000,x2=x%10000,tmp=pow2[x1][dep]*pow1[x2][dep];
        if(tmp>=phi[dep]) tmp%=phi[dep],fl=1;//!!phi[dep]
        fl|=b2[x1][dep]|b1[x2][dep];
        return tmp;
    }
    ll dfs(ll x,ll dep,ll lim)
    {
        fl=0;//打标记判断指数是否大于phi[dep] 如果大于的话,要+phi[dep]
        if(dep==lim){//递归到了期望的次方位置 就返回求出的值 
            if(x>=phi[dep]) fl=1,x%=phi[dep];
            return x;
        }
        ll xx=dfs(x,dep+1,lim);//递归 
        return calc( fl? xx+phi[dep+1] : xx ,dep);//计算那一长串的次方 如果有标记 就要加上 上一次的phi值 
    }
    void update(int s) { sum[s]=sum[s<<1]+sum[s<<1|1]; if(sum[s]>=p) sum[s]-=p; tim[s]=min(tim[s<<1],tim[s<<1|1]); }
    void build(int s,int l,int r)
    {
        if(l==r){ sum[s]=a[l]; return ; }
        build(s<<1,l,mid); build(s<<1|1,mid+1,r);
        update(s);
    }
    void modify(int s,int l,int r,int L,int R)
    //区间修改转换成单点暴力修改,但维护了修改次数,大于log次就没有必要修改了,类似于取mod的线段树一样 
    { 
        if(tim[s]>=cnt) return ;
        if(l==r) {  tim[s]++; sum[s]=dfs(a[l],0,tim[s]); return ; }//通过递归来找到第x次c的次方时,数学公式推出来的那个值 
        if(L<=mid && tim[s<<1]<cnt) modify(s<<1,l,mid,L,R);
        if(R>mid && tim[s<<1|1]<cnt) modify(s<<1|1,mid+1,r,L,R);
        update(s);
    }
    ll query(int s,int l,int r,int L,int R)
    {
        if(L<=l && r<=R) return sum[s];
        ll ans=0;
        if(L<=mid) ans=ans+query(s<<1,l,mid,L,R);
        if(R>mid)  ans=ans+query(s<<1|1,mid+1,r,L,R);
        if(ans>=p) ans-=p;//防止%多了超时 可是不会没减够吗 
        return ans;
    }
    int main()
    {
        freopen("verbinden.in","r",stdin);
        freopen("verbinden.out","w",stdout);
        n=read(); m=read(); p=read(); c=read();
        for(ri i=1;i<=n;++i) a[i]=read();
        init();
        build(1,1,n);
        while(m--){
            int op=read(),l=read(),r=read();
            if(op==0) modify(1,1,n,l,r);
            else printf("%lld
    ",query(1,1,n,l,r));
        }/**/
    }
    /*
    4 4 7 2
    1 2 3 4
    0 1 4
    1 2 4
    0 1 4
    1 1 3
    */
    View Code

     

    Day2

    T2:

    首先我们可以发现:最优策略是从后往前倒着选,如果是开的,把它和它的约数都操作一遍,按键次数cnt++。

    为什么是对的?

    一个位置只会被它后面那个位置所影响,所以说如果一个位置必须被操作,那么枚举到它了,它就必须按一次(因为我们是倒序枚举的),前面的按了不可能使它改变。

    如果cnt<=k,说明每一步都要按照最优策略来做。直接输出cnt*(n!)即可

    否则,就要求大于cnt部分,按照随机策略的按到cnt的期望步数。

    定义f [ i ]为需要按 i 个键 变成 需要按 i-1 个键 的期望次数。

    从一开始的乱选,到最后只剩 k 步然后去选最优策略的答案为:f[cnt]+f[cnt−1]+....+f[k+1] (因为f是两步的期望差)。

    转移:f [ i ] = i / n + (ni)/ n  × (f [ i ] + f [ i+1 ] + 1)

    有 i / n 的概率按到正确的键,剩下的概率按到正确的键。

    如果是正确的键,就直接转移到下一个状态。  否则,就会需要到 i+1 这一个状态再转移回来,然后还应该再从这个状态转移到下一次,按了这个键又耗费了一步,所以要+1。

    #include<bits/stdc++.h>
    using namespace std;
    #define ri register int
    #define N 100005
    #define mod 100003
    #define ll long long
    ll a[N],f[N],n,k,cnt,ans=0;
    ll read()
    {
        ll x=0,fl=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
        while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
        return fl*x;
    }
    ll quick_pow(ll a,ll k)
    {
        ll ans=1;
        while(k) { if(k&1) ans=ans*a%mod; a=a*a%mod; k>>=1; }
        return ans;
    }
    void work()
    {
        f[n+1]=0;
        for(ll i=n;i>=1;--i)
        f[i]= ( (n-i)*f[i+1] %mod + n ) %mod *quick_pow(i,mod-2) %mod ;
        for(ll i=k+1;i<=cnt;++i) ans=(ans+f[i])%mod;
        ans=(ans+k)%mod;
    }
    int main()
    {
        n=read(); k=read();
        for(ll i=1;i<=n;++i) a[i]=read();
        for(ll i=n;i>=1;--i){
            if(a[i]){
                cnt++;
                for(ll j=1;j*j<=i;++j)
                if(i%j==0) {
                    a[j]^=1;
                    if(j*j!=i) a[i/j]^=1;
                }
            }
        }
        if(cnt<=k) ans=cnt;
        else work();
        for(ll i=1;i<=n;++i) ans=ans*i%mod;
        printf("%lld
    ",ans);
        return 0;
    }
    /*
    4 0
    0 0 1 1
    */
    View Code

    T3:[六省联考2017]寿司餐厅

    最大权闭合子图问题。

    首先分析题:限制那么多,数据范围也很小,考虑网络流。

    如果不考虑花费,怎样才能得到尽量多的美味度呢?

    关键在于如何处理区间关联性:对于每一个(i,j)的区间,向比它小1的区间连边:(i+1,j)和(i,j+1)。在根据它权值的正负决定向s连边还是向t连边。(正连s,负连t

    然后求最小割(也就是最大流),用所有正权值的和 - 最小割,即最大的美味度。为什么是所有正权值的和?

    现在考虑加入花费:m*x^2+c*x

    对于 m*x^2 的部分,只需要对寿司的代号新建一个点连向 t即可

    而 c 代表选这种代号的次数,我们怎么知道这种代号被选多少次呢?

    选一份寿司必定选了相应的代号,只需要把寿司向 t 连 x 边权的边即可。

    #include<bits/stdc++.h>
    using namespace std;
    #define ri register int
    #define nn 1005
    #define N 1000005
    int tot=1,head[N],to[N<<1],nex[N<<1],w[N<<1],lev[N],cnt=0,cur[N];
    int s,t,inf=1<<30,c[nn],idd[nn],id[nn][nn],d[nn][nn];
    int read()
    {
        int x=0,fl=1; char ch=getchar();
        while(ch<'0'||ch>'9') { if(ch=='-') fl=-1; ch=getchar(); }
        while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
        return fl*x;
    }
    void add(int a,int b,int ww)
    {
        to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
        to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
    }
    queue<int> q;
    bool bfs()
    {
        memset(lev,-1,sizeof(lev));
        lev[s]=1; q.push(s);
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(ri i=head[u];i!=-1;i=nex[i]){
                int v=to[i];
                if(w[i]>0 && lev[v]==-1) lev[v]=lev[u]+1,q.push(v);
            }
        }
        return lev[t]!=-1;
    }
    int dfs(int u,int flow)
    {
        if(u==t) return flow;
        int ret=flow;
        for(ri i=cur[u];i!=-1;i=nex[i]){
            cur[u]=i;
            int v=to[i];
            if(ret<=0) break;
            if(lev[v]==lev[u]+1 && w[i]>0){
                int k=dfs(v,min(w[i],ret));
                w[i]-=k; ret-=k; w[i^1]+=k;
            }
        }
        return flow-ret;
    }
    long long sum=0;
    void dinic()
    {
        long long ans=0;
        while(bfs()){
            memcpy(cur,head,sizeof(head));
            ans+=dfs(s,inf);
        }
        printf("%lld
    ",sum-ans);
    }
    int main()
    {
        int n,m;
        memset(head,-1,sizeof(head));
        n=read(); m=read();
        s=0,t=N-5;
        for(ri i=1;i<=n;++i){
            c[i]=read();
            if(!idd[c[i]]) idd[c[i]]=++cnt,add(idd[c[i]],t,m*c[i]*c[i]);
        } 
        for(ri i=1;i<=n;++i)
         for(ri j=i;j<=n;++j)
          d[i][j]=read(),id[i][j]=++cnt,sum+= d[i][j]>0?d[i][j]:0 ;//注意这里应该是所有的正权值 
        for(ri i=1;i<=n;++i){
            add(id[i][i],idd[c[i]],inf);
            add(id[i][i],t,c[i]);
        } 
        for(ri i=1;i<=n;++i)
         for(ri j=i;j<=n;++j){
             if(d[i][j]>0) add(s,id[i][j],d[i][j]);
             else add(id[i][j],t,-d[i][j]);
             if(id[i+1][j]) add(id[i][j],id[i+1][j],inf);
             if(id[i][j-1]) add(id[i][j],id[i][j-1],inf);
        }
        dinic();
    }
    /*
    3 1
    2 3 2
    5 -10 15
    -10 15
    15
    */
    View Code
  • 相关阅读:
    tcp/ip协议listen函数中backlog參数的含义
    oracle exp实例
    js21---单体(单例)模式
    js20---接口3种方式
    js19--继承终极版本
    js18--继承方式
    js17---创建对象:构造函数式和原型组合模式、动态原型模式、稳妥构造函数式
    js16--自定义原型对象
    js---15,模拟数组的ecah方法
    js14--原型2
  • 原文地址:https://www.cnblogs.com/mowanying/p/11544067.html
Copyright © 2011-2022 走看看