• 2017.8.7离线赛题解


    总结

    一定程度上被出题组误导了。既然说是要报复社会,题目肯定难得变态

    第二题浪费了大量时间,看了第三题没怎么想认为比第二题更难,so…

    时间还是要有固定的分配啊,就目前而看反正联赛应该没这么难

    第一题$30-60min$

    第二题$60-90min$

    第三题$60-90min$

    具体自己调整,第二题和第三题已经不一定谁更难了,但最小时间肯定要达到,这几次爆$0$基本都是第三题基本在$30min$以下(上上场前两题很顺,留了时间还对拍了,所以没爆$0$)。

    务农政策

    显然就是求$min(sum(i,k,i-a+1,k-b+1)$ $-$ $tallest(i,k,i-a+1,k-b+1) times a times b)$。维护的重点也就是二维的区间最大值。

    如果直接枚举,可以发现对于枚举的$k$,每次扫描的二维区间是从上向下滑动的。可以用双端队列维护。

    70分 用上述思路,然后每次查询区间$[k,k-b+1]$用线段树,复杂度是$nmlog(n)T$,常数太大,直接爆炸。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<ctype.h>
    #define M 1005
    #define LL long long
    #define oo 2000000000
    using namespace std;
    void (int &res){
    char c;
    res=0;
    while(c=getchar(),!isdigit(c));
    do{
    res=(res<<3)+(res<<1)+(c^'0');
    }while(c=getchar(),isdigit(c));
    }
    int mp[M][M],m,n;
    LL sum[M][M];
    struct node{
    int a[M<<2],pos;
    void build(int L,int R,int p){
    if(L==R){
    a[p]=mp[pos][L];
    return;
    }
    int mid=L+R>>1;
    build(L,mid,p<<1);
    build(mid+1,R,p<<1|1);
    a[p]=max(a[p<<1],a[p<<1|1]);
    }
    int query(int l,int r,int L,int R,int p){
    if(l==L&&r==R)return a[p];
    int mid=L+R>>1;
    if(r<=mid)return query(l,r,L,mid,p<<1);
    else if(l>mid)return query(l,r,mid+1,R,p<<1|1);
    else return max(query(l,mid,L,mid,p<<1),query(mid+1,r,mid+1,R,p<<1|1));
    }
    }T[M];
    LL calc(int x,int y,int a,int b){
    return sum[x][y]-sum[x-a][y]-sum[x][y-b]+sum[x-a][y-b];
    }
    int dui[M],val[M];
    LL solve(int a,int b){
    LL re=1ll*oo*oo;
    for(int k=b;k<=m;k++){
    int l=0,r=-1;
    for(int i=1;i<=a;i++){
    val[i]=T[i].query(k-b+1,k,1,m,1);
    while(l<=r&&val[dui[r]]<val[i])r--;
    dui[++r]=i;
    }
    re=min(re,1ll*val[dui[l]]*a*b-calc(a,k,a,b));
    for(int i=a+1;i<=n;i++){
    while(l<=r&&i-dui[l]>=a)l++;
    val[i]=T[i].query(k-b+1,k,1,m,1);
    while(l<=r&&val[dui[r]]<val[i])r--;
    dui[++r]=i;
    re=min(re,1ll*val[dui[l]]*a*b-calc(i,k,a,b));
    }
    }
    return re;
    }
    int main(){
    // freopen("policy.in","r",stdin);
    // freopen("policy.out","w",stdout);
    int i,k,j;
    Rd(n),Rd(m);
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++){
    Rd(mp[i][k]);
    sum[i][k]+=mp[i][k];
    }
    }
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++)sum[i][k]+=sum[i-1][k]+sum[i][k-1]-sum[i-1][k-1];
    }
    for(i=1;i<=n;i++)T[i].pos=i,T[i].build(1,m,1);
    int q;
    Rd(q);
    for(i=1;i<=q;i++){
    int a,b;
    Rd(a),Rd(b);
    printf("%lldn",solve(a,b));
    }
    return 0;
    }

    又发现$k$从b到n枚举,每次查询的$[k,k-b+1]$是从左向右滑动的,也可以用双端队列维护。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<ctype.h>
    #define M 1005
    #define LL long long
    #define oo 2000000000
    using namespace std;
    void (int &res){
    char c;
    res=0;
    while(c=getchar(),!isdigit(c));
    do{
    res=(res<<3)+(res<<1)+(c^'0');
    }while(c=getchar(),isdigit(c));
    }
    int mp[M][M],m,n;
    LL sum[M][M];
    struct node{
    int a[M<<2],pos;
    void build(int L,int R,int p){
    if(L==R){
    a[p]=mp[pos][L];
    return;
    }
    int mid=L+R>>1;
    build(L,mid,p<<1);
    build(mid+1,R,p<<1|1);
    a[p]=max(a[p<<1],a[p<<1|1]);
    }
    int query(int l,int r,int L,int R,int p){
    if(l==L&&r==R)return a[p];
    int mid=L+R>>1;
    if(r<=mid)return query(l,r,L,mid,p<<1);
    else if(l>mid)return query(l,r,mid+1,R,p<<1|1);
    else return max(query(l,mid,L,mid,p<<1),query(mid+1,r,mid+1,R,p<<1|1));
    }
    }T[M];
    LL calc(int x,int y,int a,int b){
    return sum[x][y]-sum[x-a][y]-sum[x][y-b]+sum[x-a][y-b];
    }
    int dui[M],val[M];
    int dui1[M][M],l[M],r[M];
    LL solve(int a,int b){
    LL re=1ll*oo*oo;
    memset(l,0,sizeof(l));
    memset(r,-1,sizeof(r));
    for(int k=1;k<b;k++){
    for(int i=1;i<=n;i++){
    while(l[i]<=r[i]&&mp[i][dui1[i][r[i]]]<=mp[i][k])r[i]--;
    dui1[i][++r[i]]=k;
    }
    }
    for(int k=b;k<=m;k++){
    for(int i=1;i<=n;i++){
    while(l[i]<=r[i]&&k-dui1[i][l[i]]>=b)l[i]++;
    while(l[i]<=r[i]&&mp[i][dui1[i][r[i]]]<=mp[i][k])r[i]--;
    dui1[i][++r[i]]=k;
    }
    int L=0,R=-1;
    for(int i=1;i<=a;i++){
    val[i]=mp[i][dui1[i][l[i]]];
    while(L<=R&&val[dui[R]]<val[i])R--;
    dui[++R]=i;
    }
    re=min(re,1ll*val[dui[L]]*a*b-calc(a,k,a,b));
    for(int i=a+1;i<=n;i++){
    while(L<=R&&i-dui[L]>=a)L++;
    val[i]=mp[i][dui1[i][l[i]]];
    while(L<=R&&val[dui[R]]<val[i])R--;
    dui[++R]=i;
    re=min(re,1ll*val[dui[L]]*a*b-calc(i,k,a,b));
    }
    }
    return re;
    }
    int main(){
    // freopen("policy.in","r",stdin);
    // freopen("policy.out","w",stdout);
    int i,k,j;
    Rd(n),Rd(m);
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++){
    Rd(mp[i][k]);
    sum[i][k]+=mp[i][k];
    }
    }
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++)sum[i][k]+=sum[i-1][k]+sum[i][k-1]-sum[i-1][k-1];
    }
    for(i=1;i<=n;i++)T[i].pos=i,T[i].build(1,m,1);
    int q;
    Rd(q);
    for(i=1;i<=q;i++){
    int a,b;
    Rd(a),Rd(b);
    printf("%lldn",solve(a,b));
    }
    return 0;
    }

    后来跟yahong再讨论时,突然发现$st表$也可以,而且速度比大多数单调队列要快。实现方法跟线段树基本一样,只是询问复杂度变成$O(1)$。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<ctype.h>
    #define M 1005
    #define LL long long
    #define oo 2000000000
    using namespace std;
    void (int &res){
    char c;
    res=0;
    while(c=getchar(),!isdigit(c));
    do{
    res=(res<<3)+(res<<1)+(c^'0');
    }while(c=getchar(),isdigit(c));
    }
    int mp[M][M],m,n;
    LL sum[M][M];
    int st[M],S=10;
    struct ST{
    int a[11][M],pos;
    ST(){
    memset(a,0,sizeof(a));
    }
    void build(){
    for(int i=1;i<=m;i++)a[0][i]=mp[pos][i];
    for(int i=1;i<=S;i++){
    for(int k=1;k<=m;k++){
    a[i][k]=max(a[i-1][k],a[i-1][min(m,k+(1大专栏  2017.8.7离线赛题解pan><<i-1))]);
    }
    }
    }
    int query(int l,int r){
    int p=st[r-l+1];
    return max(a[p][l],a[p][r-(1<<p)+1]);
    }
    }T[M];
    LL calc(int x,int y,int a,int b){
    return sum[x][y]-sum[x-a][y]-sum[x][y-b]+sum[x-a][y-b];
    }
    int dui[M],val[M];
    LL solve(int a,int b){
    LL re=1ll*oo*oo;
    for(int k=b;k<=m;k++){
    int l=0,r=-1;
    for(int i=1;i<=a;i++){
    val[i]=T[i].query(k-b+1,k);
    while(l<=r&&val[dui[r]]<val[i])r--;
    dui[++r]=i;
    }
    re=min(re,1ll*val[dui[l]]*a*b-calc(a,k,a,b));
    for(int i=a+1;i<=n;i++){
    while(l<=r&&i-dui[l]>=a)l++;
    val[i]=T[i].query(k-b+1,k);
    while(l<=r&&val[dui[r]]<val[i])r--;
    dui[++r]=i;
    re=min(re,1ll*val[dui[l]]*a*b-calc(i,k,a,b));
    }
    }
    return re;
    }
    int main(){
    // freopen("policy0.in","r",stdin);
    // freopen("policy.out","w",stdout);
    int i,k,j;
    Rd(n),Rd(m);
    st[1]=0;
    for(i=1;i<=S;i++){
    for(k=(1<<(i-1))+1;k<=(1<<i);k++)st[k]=i-1;
    //st[k]=i-1而不是i,被坑了好久。
    }
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++){
    Rd(mp[i][k]);
    sum[i][k]+=mp[i][k];
    }
    }
    for(i=1;i<=n;i++){
    for(k=1;k<=m;k++)sum[i][k]+=sum[i-1][k]+sum[i][k-1]-sum[i-1][k-1];
    }
    for(i=1;i<=n;i++)T[i].pos=i,T[i].build();
    int q;
    Rd(q);
    for(i=1;i<=q;i++){
    int a,b;
    Rd(a),Rd(b);
    printf("%lldn",solve(a,b));
    }
    return 0;
    }

    刷副本

    求累乘就不用说了,主要是求首位数字。

    对于最终数字$w$,可以表示为$w=a*10^b$,其中$ain[0,10)$。$pp=log_{10}w$,$a=10^{{pp}}$,$a$就是最终答案。用对数公式$log_{x}{ab}=log_xa+log_xb$将求法转为$log_{10}a$的累加。说起来思路比较简单,但是谁能想到用这种数学公式化简?询问时$O(1)$,更新时更新所有是$x-1$因子的$R$,对于$x=1$要更新所有,所以可以在查询时加入。

    值得一提,正解其实可能被卡掉,仔细观察的话,发现用高精是从后往前求首位,正解则从前往后,是不能省略数字的,我是说正解的double保存位数有限,所以经多次加法,可能使得被省去的位数也对答案造成影响,理论上存在问题,但是double的精度还是很高,所以在实际中精度误差几乎被忽略

    所以正解可以理解为从前往后,忽略低位对于答案的影响。

    分块决策会更快。当R大于$sqrt n$时直接爆力,小于时在更新是维护。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<vector>
    #include<math.h>
    #define M 200005
    #define P 1000000007
    #define db double
    #define lowbit 0.000000000001
    using namespace std;
    vector<int>p[M];
    int n,a[M],q;
    db ans1[M];
    int ans2[M];
    int fast(int x,int t){
    int re=1;
    while(t){
    if(t&1)re=1ll*re*x%P;
    x=1ll*x*x%P;
    t>>=1;
    }
    return re;
    }
    int get(db x){
    int re=(pow(10,x-(int)x)+lowbit);
    if(re==10)re=1;
    return re;
    }
    int main(){
    // freopen("instance16.in","r",stdin);
    // freopen("p.out","w",stdout);
    int i,k,j;
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i]),ans2[i]=1;
    for(i=1;i<=n;i++){
    for(k=i;k<=n;k+=i)p[k].push_back(i);
    }
    for(i=1;i<=n;i++){
    for(k=1+i;k<=n;k+=i){
    ans2[i]=1ll*ans2[i]*a[k]%P;
    db b=log10(a[k]);
    ans1[i]+=b;
    }
    }
    scanf("%d",&q);
    for(i=1;i<=q;i++){
    int id,x,y;
    scanf("%d",&id);
    if(id==1){
    scanf("%d%d",&x,&y);
    if(x==1){
    a[1]=y;
    continue;
    }
    int f=fast(a[x],P-2);
    db l=log10(a[x]),ly=log10(y);
    for(k=0;k<p[x-1].size();k++){
    int pp=p[x-1][k];
    ans2[pp]=1ll*ans2[pp]*f%P;
    ans2[pp]=1ll*ans2[pp]*y%P;
    ans1[pp]-=l,ans1[pp]+=ly;
    }
    a[x]=y;
    }
    else {
    scanf("%d",&x);
    db tt=ans1[x]+log10(a[1]);
    printf("%d %dn",get(tt),1ll*ans2[x]*a[1]%P);
    }
    }
    return 0;
    }

    圣杯战争

    答案是一条边应该是比较显然的。

    所以可以把图改成生成树,并以$1$为根。为了方便维护,把一条边对答案的贡献放到边上深度小的点上。然后对于每一个节点,建一棵关于儿子颜色的线段树。每次询问$max(query(1,c[i]-1),query(c[i]+1,K))$,并且由于颜色会改变,所以同一颜色的点都要被记录,每次弹出最小的,可以用multiset维护。直接开线段树是开不下的,所以是动态线段树。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    #include<iostream>
    #include<algorithm>
    #include<string.h>
    #include<ctype.h>
    #include<set>
    #define M 200005
    #define oo 2000000000
    using namespace std;
    void (int &res){
    char c;
    res=0;
    while(c=getchar(),!isdigit(c));
    do{
    res=(res<<3)+(res<<1)+(c^'0');
    }while(c=getchar(),isdigit(c));
    }
    struct node{
    int to,v,nxt;
    }edge[M<<1];
    int head[M],cnt=0;
    multiset<int>mul[M<<2],ans;
    int SS=0;
    int fa[M],f[M],vf[M],C[M],res[M];
    int n,m,q,K;
    int rt[M*40],ls[M*40],rs[M*40],val[M*40],id[M*40],S=0;
    struct nodt{
    int x,y,v;
    bool operator <(const nodt &p)const{
    return v<p.v;
    }
    }e[M<<1];
    int getfa(int x){
    if(x==fa[x])return x;
    return fa[x]=getfa(fa[x]);
    }
    void add(int x,int y,int v){
    edge[++cnt]=(node){y,v,head[x]};
    head[x]=cnt;
    }
    void up(int p){
    val[p]=min(val[ls[p]],val[rs[p]]);
    }
    void update(int x,int v,bool f,int L,int R,int &p){
    if(!p)p=++S,val[p]=oo;
    if(L==R){
    if(!id[p])id[p]=++SS;
    if(f)mul[id[p]].insert(v);
    else mul[id[p]].erase(mul[id[p]].find(v));
    if(mul[id[p]].size())val[p]=*(mul[id[p]].begin());
    else val[p]=oo;
    return;
    }
    int mid=L+R>>1;
    if(x<=mid)update(x,v,f,L,mid,ls[p]);
    else update(x,v,f,mid+1,R,rs[p]);
    up(p);
    }
    int query(int l,int r,int L,int R,int &p){
    if(!p||l>r)return oo;
    if(l==L&&r==R)return val[p];
    int mid=L+R>>1;
    if(r<=mid)return query(l,r,L,mid,ls[p]);
    else if(l>mid)return query(l,r,mid+1,R,rs[p]);
    else return min(query(l,mid,L,mid,ls[p]),query(mid+1,r,mid+1,R,rs[p]));
    }
    void dfs(int now,int las){
    f[now]=las;
    for(int i=head[now];i;i=edge[i].nxt){
    node p=edge[i];
    if(p.to==las)continue;
    dfs(p.to,now);
    vf[p.to]=p.v;
    update(C[p.to],p.v,1,1,K,rt[now]);
    }
    if(rt[now]){
    res[now]=min(query(1,C[now]-1,1,K,rt[now]),query(C[now]+1,K,1,K,rt[now]));
    ans.insert(res[now]);
    }
    }
    int main(){
    // freopen("hack0.in","r",stdin);
    // freopen("p.out","w",stdout);
    int i,k,j;
    Rd(n),Rd(m),Rd(K),Rd(q);
    for(i=1;i<=n;i++)fa[i]=i;
    for(i=1;i<=m;i++)Rd(e[i].x),Rd(e[i].y),Rd(e[i].v);
    for(i=1;i<=n;i++)Rd(C[i]);
    sort(e+1,e+1+m);
    for(i=1;i<=m;i++){
    int x=e[i].x,y=e[i].y;
    int a=getfa(x),b=getfa(y);
    if(a==b)continue;
    fa[a]=b;
    add(x,y,e[i].v);
    add(y,x,e[i].v);
    }
    val[0]=oo;
    dfs(1,0);
    for(int i=1;i<=q;i++){
    int x,y;
    Rd(x),Rd(y);
    if(C[x]!=y){
    if(rt[x]){
    ans.erase(ans.find(res[x]));
    res[x]=min(query(1,y-1,1,K,rt[x]),query(y+1,K,1,K,rt[x]));
    ans.insert(res[x]);
    }
    if(x!=1){
    int ff=f[x];
    ans.erase(ans.find(res[ff]));
    update(C[x],vf[x],0,1,K,rt[ff]);
    update(y,vf[x],1,1,K,rt[ff]);
    res[ff]=min(query(1,C[ff]-1,1,K,rt[ff]),query(C[ff]+1,K,1,K,rt[ff]));
    ans.insert(res[ff]);
    }
    C[x]=y;
    }
    printf("%dn",*(ans.begin()));
    }
    return 0;
    }


  • 相关阅读:
    pidgin的未认证解决办法
    题解【洛谷P1074】[NOIP2009]靶形数独
    题解【洛谷P1315】[NOIP2011]观光公交
    题解【BZOJ4145】「AMPPZ2014」The Prices
    题解【洛谷P4588】[TJOI2018]数学计算
    题解【洛谷P3884】[JLOI2009]二叉树问题
    题解【SP8002】HORRIBLE
    树链剖分学习笔记
    题解【洛谷P1807】最长路_NOI导刊2010提高(07)
    题解【洛谷P1995】口袋的天空
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12347340.html
Copyright © 2020-2023  润新知