牛客网暑期ACM多校训练营(第二场)

A
签到

#include <bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long q,k;
long long dp[100003][2];
int main(){
    scanf("%lld%lld",&q,&k);
    dp[0][0]=1;
    for(long long i=1;i<=100000;i++){
        dp[i][0]=(dp[i-1][0]+dp[i-1][1])%mod;
        if(i>=k)dp[i][1]=dp[i-k][0];
    }
    for(long long i=1;i<=100000;i++){
        dp[i][0]=(dp[i][0]+dp[i-1][0])%mod;
        dp[i][1]=(dp[i][1]+dp[i-1][1])%mod;
    }
    while(q--){
        long long l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",(dp[r][0]-dp[l-1][0]+dp[r][1]-dp[l-1][1]+2*mod)%mod);
    }
}

C
首先把问题转化为平面上有n个点,有m个询问,每个询问给出一个点,查询该点与n个点连线的斜率的最小值。离线询问,考虑每个点左边的点的贡献,发现只有在上凸壳上的点才有可能取到最小值,用一个栈维护上凸壳。并且发现其实查询时如果假装把查询的点插入凸包,把凹进去的点都出栈后,和栈顶的点的连线的斜率就是最小值,于是我们二分这个栈顶下标即可。注意使用叉积而不是斜率大小作为出入栈判断。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m;
struct node{
    ll x,y,id;
    void print(){
        cout<<id<<' '<<x<<' '<<y<<endl;
    }
    double u(){
        return -1.0*y/x;
    }
}rec[100003];
ll tot=0;
node operator-(const node& a,const node& b){
    return (node){a.x-b.x,a.y-b.y,0};
}
ll operator*(const node& a,const node& b){
    return a.x*b.y-a.y*b.x;
}
bool operator<(const node& a,const node& b){
    return a.x<b.x;
}
node st[50003];
ll top;
double ans[50003];
bool check(int last,node p){
    if(last<=0)return true;
    return (p-st[last-1])*(st[last]-st[last-1])>0;
}
void insert(node p){
    while(!check(top-1,p))top--;
    st[top++]=p;
}
double query(node p){
    if(top==0)return 0;
    int l=0,r=top-1;
    while(1)
    {
        if(r-l<=1){
            if(check(r,p))return (p-st[r]).u();
            else return (p-st[l]).u();
        }
        int mid=(l+r)/2;
        if(check(mid,p))l=mid;
        else r=mid;
    }
}
void work(){
    top=0;
    for(ll i=1;i<=tot;i++){
        //rec[i].print();
        if(rec[i].id==0){
            insert(rec[i]);
        }
        else ans[rec[i].id]=max(ans[rec[i].id],query(rec[i]));
    }
}
int main(){
    memset(ans,0,sizeof(ans));
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++){
        ll x,y;
        scanf("%lld%lld",&x,&y);
        rec[++tot]=(node){x,y,0};
    }
    scanf("%lld",&m);
    for(ll i=1;i<=m;i++){
        ll x,y;
        scanf("%lld%lld",&x,&y);
        rec[++tot]=(node){x,y,i};
    }
    sort(rec+1,rec+1+tot);
    work();
    reverse(rec+1,rec+1+tot);
    work();
    for(ll i=1;i<=m;i++){
        if(ans[i]==0){
            printf("No cross\n");
        }
        else printf("%.15f\n",ans[i]);
    }
}

D
签到

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

int t,n,a[100007],py;
long long ans,last;

int main (){
    cin>>t;
    while (t--){
        scanf ("%d",&n);
        for (int i=1;i<=n;i++){
            scanf ("%d",&a[i]);
            // cout<<a[i]<<' ';
        }
        ans=py=0;
        last=a[1];
        a[n+1]=-1;
        for (int i=2;i<=n+1;i++){
            if (a[i]<a[i-1]){
                if (a[i-1]>last){
                    ans+=(long long)a[i-1]-last;
                    py+=2;
                }
                last=a[i];
            }
        }
        cout<<ans<<' '<<py<<endl;
    }
    return 0;
}

G
首先枚举汇集的位置,二分长度,考虑在长度以内的都移到中心,判断价值是否足够,这个是两个log的。考虑把二分长度变成二分右端点,又发现在中点递增的情况下,右端点若减对答案没有贡献,于是右端点是递增的,成功去掉一个log。

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

typedef long long ull;
int l[500007],R,n,a[500007],x[500007];
ull T,sa[500007],sax[500007],tans,ans,tmp;

inline int half (int l,int r,int xm,int lim){
    if (l>=r)return l;
    int mid=(l+r)>>1;
    if (xm-x[mid]<=lim)return half (l,mid,xm,lim);
    else return half (mid+1,r,xm,lim);
}

inline int half2 (int l,int r,int mid){
    if (l>=r)return l;
    int L=(l+r)>>1;
    ull tmp=(sax[R]-sax[mid])-(sa[R]-sa[mid])*(ull)x[mid];
    tmp+=(sa[mid]-sa[L-1])*(ull)x[mid]-(sax[mid]-sax[L-1]);
    if (tmp<=T)return half2 (l,L,mid);
    return half2 (L+1,r,mid);
}

inline int pd (int mid,int r){
    int L;
    L=half (1,mid,x[mid],x[r]-x[mid]);//x[mid]-x[l]<x[r]-x[mid]
    ull tmp=(sax[R]-sax[mid])-(sa[R]-sa[mid])*(ull)x[mid];
    tmp+=(sa[mid]-sa[L-1])*(ull)x[mid]-(sax[mid]-sax[L-1]);
    if (tmp<=T)return L;
    return -L;
}

inline void fig (int mid,int L){
    ull tmp=(sax[R]-sax[mid])-(sa[R]-sa[mid])*(ull)x[mid];
    tmp+=(sa[mid]-sa[L-1])*(ull)x[mid]-(sax[mid]-sax[L-1]);
    tans=sa[R]-sa[L-1];
    if (L>1){
        if (R==n||x[mid]-x[L-1]<=x[R+1]-x[mid]){
            tans+=(T-tmp)/(ull)(x[mid]-x[L-1]);
        }
        else tans+=(T-tmp)/(ull)(x[R+1]-x[mid]);
    }
    else{
        if (R<n)tans+=(T-tmp)/(ull)(x[R+1]-x[mid]);
    }
    ans=max(ans,tans);
}

void work (){
    R=1;
    ans=tans=0;
    for (int i=1;i<=n;i++){
        while (R<=n){
            l[R]=pd (i,R);
            if (l[R]<0)break;
            R++;
        }
        R--;
        if (R==n)l[R+1]=1;
        else l[R+1]=-l[R+1];
        int L=half2 (l[R+1],l[R],i);
        fig (i,L);
    }
    cout<<ans<<endl;
}

int main (){
    cin>>n>>T;
    T/=2;
    for (int i=1;i<=n;i++){
        scanf ("%d",&x[i]);
    }
    sa[0]=sax[0]=0;
    for (int i=1;i<=n;i++){
        scanf ("%d",&a[i]);
        sa[i]=sa[i-1]+(ull)a[i];
        sax[i]=sax[i-1]+(ull)a[i]*(ull)x[i];
    }
    work ();
    return 0;
}

H
使用树形DP。
f[i][j] 表示以 i 为根的子树选了 j 条路径的最大权值和。
g[i][j] 表示以 i 为根的子树选了 j 条路径加一条包含 i 的竖直链的最大权值和。
共有三种更新:
儿子取2个 f 加上根节点更新父节点的 g
儿子取1个 f 加上根节点更新父节点的 f
儿子取0个 f 不加上根节点更新父节点的 g
用类似背包的做法完成单节点上的dp即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline void gmax(ll &a,ll b){
    a=max(a,b);
}
vector<int>G[400003];
int x[400003];
ll f[400003][4];
ll g[400003][4];
int n;
void dfs(int u,int fa){
    ll a[4][3]={0};
    ll b[4][3]={0};
    ll c[4][3]={0};
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(v==fa)continue;
        dfs(v,u);
        memset(b,0,sizeof(b));
        for(int i=0;i<4;i++){
            for(int j=0;j<2;j++){
                for(int k=1;k<=3;k++){
                    if(i==0&&j)continue;
                    int it=i+k;
                    int jt=j+1;
                    if(jt==2)it--;
                    if(it>3)continue;
                    gmax(b[it][jt],a[i][j]+f[v][k]);
                }
            }
        }
        memset(c,0,sizeof(c));
        for(int i=0;i<4;i++){
            for(int j=0;j<=2;j++){
                for(int k=1;k<=3;k++){
                    if(i==0&&j)continue;
                    int it=i+k;
                    if(it>3)continue;
                    gmax(c[it][j],a[i][j]+g[v][k]);
                }
            }
        }
        for(int i=0;i<4;i++){
            for(int j=0;j<3;j++){
                gmax(a[i][j],b[i][j]);
                gmax(a[i][j],c[i][j]);
            }
        }
    }

    for(int i=1;i<4;i++)f[u][i]=a[i][1]+x[u];
    for(int i=1;i<4;i++)g[u][i]=a[i][0];
    for(int i=1;i<4;i++)gmax(g[u][i],a[i][2]+x[u]);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&x[i]);
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,0);
    printf("%lld\n",g[1][3]);
}

I
考虑没有障碍的情况,注意到4个角上最多有4个贡献,把角刨除之后问题规模就变成了n-2,可以递归解决。进一步发现其实只有对角线上的点有贡献,规模是 o(n) o ( n ) 的,每个障碍把和它同一行的横向行走方案去除,把和它同一列的纵向行走方案去除。把所有对角线上的点每四个分一组,暴力枚举行走方案即可。

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

int n,m,a[5],b[5],ans;
bool fx[100007],fy[100007],h[5],s[5];

void work (){
    int i,j;
    for (i=1;i<=n/2;i++){
        a[1]=i;b[1]=i;
        a[2]=i;b[2]=n-i+1;
        a[3]=n-i+1;b[3]=i;
        a[4]=n-i+1;b[4]=n-i+1;
        for (j=1;j<=4;j++){
            h[j]=fx[a[j]];
            s[j]=fy[b[j]];
            // cout<<j<<' '<<h[j]<<' '<<s[j]<<endl;
        }
        int tmp1=(!h[1])+(!s[2])+(!s[3])+(!h[4]);
        int tmp2=(!s[1])+(!h[2])+(!h[3])+(!s[4]);
        // cout<<i<<' '<<max(tmp1,tmp2)<<endl;
        // if (max(tmp1,tmp)>2)
        ans+=max(tmp1,tmp2);
    }
    if (n%2==1){
        int tx=n/2+1;

        // cout<<tx<<' '<<s
        if (fx[tx]==0||fy[tx]==0)ans++;
    }
    printf("%d\n",ans);
}

int main (){
    int x,y;
    while(~scanf ("%d%d",&n,&m)){
        memset (fx,0,sizeof (fx));
        memset (fy,0,sizeof (fy));
        ans=0;
        for (int i=1;i<=m;i++){
            scanf ("%d%d",&x,&y);
            fx[x]=1;
            fy[y]=1;
        }
        work();
    }
    return 0;
}

J
每个点维护施肥次数 C0 C 0 、施肥种类和 C1 C 1 、施肥种类平方和 C2 C 2 ,一个种类为 C C 的点活到最后当且仅当C0C=C1 C0C2=C2 C 0 ∗ C 2 = C 2 。这个可以二维前缀和 O(nm) O ( n ∗ m ) ,我写的多了一个log。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
ll c0[1000003];
ll c1[1000003];
ll c2[1000003];
inline int lowbit(int x){
    return x&(-x);
}
void update(int x,int col,int op){
    while(x<=1000000){
        c0[x]+=op;
        c1[x]+=op*col;
        c2[x]+=1ll*op*col*col;
        x+=lowbit(x);
    }
}
bool query(int x,int col){
    ll s0=0,s1=0,s2=0;
    while(x){
        s0+=c0[x];
        s1+=c1[x];
        s2+=c2[x];
        x-=lowbit(x);
    }
    if(s0*col==s1&&s0*col*col==s2)return false;
    return true;
}
int n,m,q;
struct que{
    int x,y,col,typ;
}rec[5000003];
bool operator<(const que& a,const que& b){
    if(a.x==b.x){
        return abs(a.typ)>abs(b.typ);
    }
    return a.x<b.x;
}
int tot=0;
int ans=0;
int main(){
    n=read();
    m=read();
    q=read();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int col=read();
            rec[++tot]=(que){i,j,col,0};
        }
    }
    int xl,yl,xr,yr,col;
    for(int i=1;i<=q;i++){
        xl=read();
        yl=read();
        xr=read();
        yr=read();
        col=read();
        rec[++tot]=(que){xl,yl,col,1};
        rec[++tot]=(que){xl,yr+1,col,-1};
        rec[++tot]=(que){xr+1,yl,col,-1};
        rec[++tot]=(que){xr+1,yr+1,col,1};
    }
    sort(rec+1,rec+1+tot);
    for(int i=1;i<=tot;i++){
        if(rec[i].typ==0){
            if(query(rec[i].y,rec[i].col)){
                ans++;
            }
        }
        else {
            update(rec[i].y,rec[i].col,rec[i].typ);
        }
    }
    printf("%d\n",ans);
}

K
把一行字符串hash成一个ll,做一遍kmp得到p,列方向再做一遍得到q。
所有大小为p*q的子矩阵最大值的最小值做两遍经典的窗口单调队列即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=9999999999999937ll;

ll gethash(string s){
    int len=s.length();
    ll ret=0;
    for(int i=0;i<len;i++){
        ret*=131;
        ret+=s[i]-'a'+3;
        ret%=mod;
    }
    return ret;
}
int a[1000003];
vector<int>mx[1000003];
vector<string>s;
char tmp[1000003];
int n,m;

ll s1[1000003];
int f1[1000003];
ll s2[1000003];
int f2[1000003];

int calc(ll s[],int f[],int n){
    f[1]=0;
    for(int i=2;i<=n;i++){
        int j=f[i-1];
        while(j&&s[j+1]!=s[i])j=f[j];
        if(s[j+1]==s[i])j++;
        f[i]=j;
    }
    return n-f[n];
}

int que[1000003];
int head,tail;

int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        scanf("%s",tmp);
        s.push_back(tmp);
        s1[i+1]=gethash(tmp);
    }
    for(int j=0;j<m;j++){
        string p="";
        for(int i=0;i<n;i++)p+=s[i][j];
        s2[j+1]=gethash(p);
    }
    int p=calc(s1,f1,n);
    int q=calc(s2,f2,m);
    for(int i=0;i<n;i++){
        head=1,tail=0;
        for(int j=0;j<m;j++){
            scanf("%d",&a[j]);
            if(que[head]==j-q)head++;
            while(head<=tail&&a[que[tail]]<=a[j])tail--;
            que[++tail]=j;
            if(j>=q-1)mx[i].push_back(a[que[head]]);
        }
    }
    int ans=1e9+7;
    for(int j=0;j<m-q+1;j++){
        head=1,tail=0;
        for(int i=0;i<n;i++){
            a[i]=mx[i][j];
            if(que[head]==i-p)head++;
            while(head<=tail&&a[que[tail]]<=a[i])tail--;
            que[++tail]=i;
            if(i>=p-1)ans=min(ans,a[que[head]]);
        }
    }
    printf("%lld\n",1ll*ans*(p+1)*(q+1));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值