Educational Codeforces Round 85 (Div. 2) / contest 1334


题目地址:https://codeforces.com/contest/1334




A Level Statistics

题意

思路

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        int n=read(),p,c,pp=0,cc=0,flag=0;
        while(n--)
        {
            p=read(),c=read();
            if(p<pp || c<cc || p<c || p-pp<c-cc) flag=1;
            pp=p; cc=c;
        }
        puts(flag?"NO":"YES");
    }

    return 0;
}



B Middle Class

题意

思路

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
LL a[maxn],x;

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        int n=read(),i=1; x=read();
        REP(i,1,n) a[i]=read();
        sort(a+1,a+n+1,greater<int>());
        REP(i,1,n) a[i]+=a[i-1];
        for(;i<=n && a[i]>=x*i;i++);
        printf("%d\n",i-1);
    }

    return 0;
}



C Circle of Monsters

题意:有 n 只怪兽围成一圈,每只怪兽有一个血量 a[i],一个 d[i] 表示这只怪兽死了之后会对下一只造成多少伤害。用一颗子弹可以对一只怪兽造成 1 点伤害,问最少的子弹花费,使得所有怪兽死掉。

思路:可以看出只有 n 种可能性,那就是选择某一只怪兽杀死,然后依次往下杀。所以可以一开始计算一个前缀和,然后枚举即可。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=8e5+5;
LL a[maxn],b[maxn],c[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        int n=read();
        REP(i,1,n) scanf("%lld%lld",&a[i],&b[i]);
        REP(i,1,n*2+5) c[i]=max(a[i%n+1]-b[(i-1)%n+1],0ll)+c[i-1];
        LL ans=1e18;
        REP(i,1,n) ans=min(ans,a[i]+c[n+i-2]-c[i-1]);
        printf("%lld\n",ans);
    }

    return 0;
}



D Minimum Euler Cycle

题意:给出一个 n 个结点的完全有向图,要找到一个字典序最小的欧拉环,输出其中 [l, r] 部分的结点。

思路:可以发现字典序最小的一定是这样的(以 n=5 举例):12131415|232425|3435|45|1,这么想的一个依据是有向图存在欧拉环当且仅当所有结点的入度等于出度,在这个前提下贪心的思考,就可以想出来。

然后就找到规律,输出要求部分即可。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int a[200005],ans[200005];
int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        int n=read(); LL l,r,ll=1,rr=0;
        scanf("%lld%lld",&l,&r);
        REP(i,1,n-1)
        {
            rr+=(n-i)*2;
            if(max(l,ll)<=min(r,rr))
            {
                for(LL j=ll;j<=rr;j++)
                    a[j-ll]=(j-ll)&1?(j-ll)/2+i+1:i;
                for(LL j=max(l,ll);j<=min(r,rr);j++)
                    ans[j-l]=a[j-ll];
            }
            ll=rr+1;
        }
        if(ll<=r) ans[r-l]=1;
        REP(i,0,r-l) printf("%d ",ans[i]);
        puts("");
    }

    return 0;
}



E Divisor Paths

题意:给定一个正整数 D,把其所有因数拿出来做结点,然后连无向边,两个结点 x 和 y(x>y) 有一条边相连,当且仅当 x/y 为一个质数,且这条边的边权值为不属于 y 的 x 的因子的个数。现在有若干个询问,每次询问两个点之间的最短路。

思路:可以发现两个结点如果有边相连,边权值为大的那个的因子个数减去小的的因子个数。并且,一个结点通过边转移到另一个结点,要么乘一个质数,要么除以一个质数。所以一开始先把D的所有质因数处理出来,那么就只能通过乘除这些质因数进行转移。

假设询问的两个点为 u 和 v(u>v) ,如果 u%v==0,那么从 u 走到 v 的最短路一定是:u 不停地除以一个质数,直到 v 为止,设 d[x] 为 x 结点因子个数,那么从 u 到 v 的最短路就是 d[u]-d[x]+d[x]-d[y]+ … + d[z]-d[v] = d[u]-d[v]。而从 u 到 v 的最短路的条数,就是除以质数的次数的阶乘除以每个质数出现次数的阶乘的积(唉其实就是一个可重全排列)。如果 v 不能整除 u 呢?很容易想到以 gcd(u, v) 作为中间点,就是最短的。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int M=998244353;
int fac[1005],facni[1005],tot,t[1005];
LL D,u,v,y[1005];

int ksm(int x,int n)
{
    int ans=1;
    while(n)
    {
        if(n&1) ans=1ll*ans*x%M;
        x=1ll*x*x%M;
        n>>=1;
    }
    return ans;
}

int cal(LL u,LL v)
{
    LL x=u/v;
    int r=0;
    for(int i=0;i<tot && y[i]<=x;i++)
        if(x%y[i]==0)
        {
            r++;
            x/=y[i];
            t[i]++;
            i=-1;
        }
    r=fac[r];
    REP(i,0,tot-1) if(t[i]) r=1ll*r*facni[t[i]]%M,t[i]=0;
    return r;
}

int main()
{
    //freopen("input.txt","r",stdin);
    fac[0]=1;
    REP(i,1,1000)
    {
        fac[i]=1ll*fac[i-1]*i%M;
        facni[i]=ksm(fac[i],M-2);
    }
    scanf("%lld",&D);
    LL x=D;
    set<LL> s;
    for(LL i=2;i*i<=x;i++)
        if(x%i==0)
        {
            s.insert(i);
            x/=i;
            i=1;
        }
    if(x!=1) s.insert(x);
    for(set<LL>::iterator i=s.begin();i!=s.end();i++)
        y[tot++]=*i;

    int q=read();
    while(q--)
    {
        scanf("%lld%lld",&u,&v);
        LL g=__gcd(u,v);
        printf("%d\n",1ll*cal(u,g)*cal(v,g)%M);
    }

    return 0;
}



F Strange Function

题意:设 a 为一个数组,f(a) 的作用是生成一个严格单调数组 b,生成的方法为:从前往后遍历 a,如果 a[i] 严格大于之前所有的数,就把 a[i] 放进 b 中。现在三个数组 a,b,p,p[i] 表示删去 a[i] 的代价,要求最小的代价,使得 f(a)=b 。

思路:dp。设 d(i, j) 表示处理完 a 的前 i 个数,使得 b 的前 j 个数被选出的最小花费,那么有:
i f ( a i < b j ) : d ( i , j ) = d ( i − 1 , j ) + m i n ( 0 , p i ) i f ( a i > b j ) : d ( i , j ) = d ( i − 1 , j ) + p i i f ( a i = b j ) : d ( i , j ) = m i n { d ( i − 1 , j − 1 ) ,   d ( i − 1 , j ) + m i n ( 0 , p i ) } \begin{aligned} if(a_i<b_j):& \quad d(i,j)=d(i-1,j)+min(0,p_i) \\ if(a_i>b_j):& \quad d(i,j)=d(i-1,j)+p_i \\ if(a_i=b_j):& \quad d(i,j)=min\{d(i-1,j-1), \ d(i-1,j)+min(0,p_i)\} \end{aligned} if(ai<bj):if(ai>bj):if(ai=bj):d(i,j)=d(i1,j)+min(0,pi)d(i,j)=d(i1,j)+pid(i,j)=min{d(i1,j1), d(i1,j)+min(0,pi)}
然后发现,i 这一维肯定是可以不用的,然后考虑先枚举 i ,再枚举 j 进行计算,对于每个 a[i], j 这一维除了 a[i]==b[j] 需要单独更新之外,其它好像都是一个大区间同时加一个与 j 无关的数,那不就用线段树维护就好了吗。

这还是我第一次见到居然可以用线段树优化dp的操作,真是让人吃惊。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

template <class T>
struct segment_tree_sum
{
    #define chl (k<<1)
    #define chr (k<<1|1)
    #define mid ((l+r)>>1)
    #define inf 1e16
    T *t,*tag;
    int n;

    segment_tree_sum(int n) {t=new T[n<<3](); tag=new T[n<<3](); this->n=n;}

    void push_up(int k) {t[k]=t[chl]+t[chr];}

    void push_down(int k,int l,int r)
    {
        if(!tag[k]) return;
        t[chl]+=tag[k]*(mid-l+1); t[chr]+=tag[k]*(r-mid);
        tag[chl]+=tag[k]; tag[chr]+=tag[k]; tag[k]=0;
    }

    void build(int k,int l,int r)
    {
        if(l>r) return;
        if(l==r) {t[k]=inf; return;}
        build(chl,l,mid); build(chr,mid+1,r);
        push_up(k);
    }
    void build() {build(1,0,n);}

    void update_add(int k,int l,int r,int ll,int rr,T x)
    {
        if(l>rr || ll>r || l>r) return;
        if(l>=ll && r<=rr) {t[k]+=x*(r-l+1); tag[k]+=x; return;}
        push_down(k,l,r);
        update_add(chl,l,mid,ll,rr,x); update_add(chr,mid+1,r,ll,rr,x);
        push_up(k);
    }
    void update_add(int ll,int rr,T x) {update_add(1,0,n,ll,rr,x);}

    T query(int k,int l,int r,int ll,int rr)
    {
        if(l>rr || ll>r) return 0;
        if(l>=ll && r<=rr) return t[k];
        push_down(k,l,r);
        return query(chl,l,mid,ll,rr)+query(chr,mid+1,r,ll,rr);
    }
    T query(int ll,int rr) {return query(1,0,n,ll,rr);}
};

const int maxn=5e5+5;
int a[maxn],b[maxn],p[maxn],n,m;

int main()
{
    //freopen("input.txt","r",stdin);
    n=read();
    REP(i,1,n) a[i]=read();
    REP(i,1,n) p[i]=read();
    m=read();
    REP(i,1,m) b[i]=read();
    segment_tree_sum<LL> t(m);
    t.build();
    t.update_add(0,0,-inf);
    REP(i,1,n)
    {
        int w=lower_bound(b+1,b+m+1,a[i])-b;
        int l1=0,r1=w-1,l2=w,r2=m;
        if(b[w]==a[i])
        {
            l2++;
            LL x=min(t.query(w-1,w-1),t.query(w,w)+min(0,p[i]));
            t.update_add(w,w,x-t.query(w,w));
        }
        //cout<<l1<<' '<<r1<<' '<<l2<<' '<<r2<<endl;
        if(r1>=l1) t.update_add(l1,r1,p[i]);
        if(r2>=l2) t.update_add(l2,r2,min(0,p[i]));
    }
    LL x=t.query(m,m);
    if(x<inf/10) printf("YES\n%lld",x);
    else puts("NO");

    return 0;
}



G Substring Search

题意:两个字符串 s 和 t 匹配,当且仅当 len(s)=len(t) 且对于任意 i,s[i]=t[i] || p(s[i])=t[i] (这里的 p 的意义我变化了一下,是等价的),现给出两个字符串 s 和 t,问 t 的每个长度为 len(s) 的子串是否和 s 匹配。

思路:其实这就是一个类似带通配符的匹配问题,这里构造的匹配函数就是两个差的平方的积,然后用FFT计算完全匹配函数就可以了,但不知道为啥怎么也写不对。

我估计我得好好弄一下FFT才知道为什么了。

代码


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值