Codeforces Round #602 (Div. 2) / contest 1262


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



A Math Problem

题意

思路

代码

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        int n=read(),a=1e9+5,b=-1e9-5;
        REP(i,1,n)
        {
            int l=read(),r=read();
            a=min(a,r); b=max(b,l);
        }
        printf("%d\n",a<=b?b-a:0);
    }

    return 0;
}



B Box

题意:给定一个数组q,该数组记录的是某个排列的前缀最大值,求一个满足该数组的排列,或者说明不可能满足。

思路:首先如果 q[i]<i 或者 q[i]<q[i-1] ,那么肯定不存在这样的排列;然后假设排列为 a,一定有 a[1]=q[1],对于 k>1,如果 q[k]>q[k-1],那么必有 a[k]=q[k],否则让 a[k] 从小到大取没有分配过的数,这样就可以构造出来。

代码

const int maxn=1e5+5;
int n,t,q[maxn],vis[maxn],ans[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    t=read();
    while(t--)
    {
        n=read();
        REP(i,1,n) q[i]=read();
        ans[1]=q[1];
        vis[q[1]]=1;
        int k=1,flag=0;
        REP(i,2,n)
        {
            if(q[i]<i || q[i]<q[i-1]) {flag=1; break;}
            else if(q[i]>q[i-1]) ans[i]=q[i],vis[q[i]]=1;
            else
            {
                while(k<=n && vis[k]) k++;
                ans[i]=k;
                vis[k++]=1;
            }
        }
        if(flag) puts("-1");
        else
        {
            REP(i,1,n) printf("%d ",ans[i]);
            puts("");
        }
        memset(vis,0,sizeof(vis[0])*(n+2));
    }

    return 0;
}



C Messy

题意:有一个长度为 n 括号序列(左括号和右括号数目一样),有一种操作可以使得连续区间翻转,求一个翻转操作方案(不要求次数最少),使得翻转之后的序列有 k 个前缀是合法的括号序列(也就是匹配)。

思路:假设 n=10,那么 k=1 的方案为 ((((())))),k=2 的方案为 ()(((()))),k=3 的方案为 ()()((())) ,以此类推;故我们从前往后逐个检查括号,不满足方案的就往后找到最近的相反括号,翻转这一段区间即可。这么做复杂度为 O(n^2)。

代码

const int maxn=2005;
int T,n,k,a[maxn],l[maxn],r[maxn],ans;
char s[maxn];

int find_next(int i)
{
    int j=i+1;
    while(a[j]==a[i]) j++;
    return j;
}

void update(int i)
{
    int j=find_next(i);
    l[ans]=i; r[ans++]=j;
    reverse(a+i,a+j+1);
}

int main()
{
    //freopen("input.txt","r",stdin);
    T=read();
    while(T--)
    {
        n=read(),k=read(); ans=0;
        scanf("%s",s+1);
        REP(i,1,n) a[i]=s[i]==')';
        REP(i,1,n)
        {
            if(i<=(k-1)*2 && (i&1) && a[i]) update(i);
            else if(i<=(k-1)*2 && !(i&1) && !a[i]) update(i);
            else if(i>(k-1)*2 && i<=n/2+k-1 && a[i]) update(i);
            else if(i>(k-1)*2 && i>n/2+k-1 && !a[i]) update(i);
        }
        printf("%d\n",ans);
        REP(i,0,ans-1) printf("%d %d\n",l[i],r[i]);
    }

    return 0;
}



D Optimal Subsequences

题意:给定一个长度为 n(2e5)的数组,它的长度为 k 的最佳子序列定义为:子序列(不要求连续)长度为k,元素和在所有相同长度子序列中最大,满足以上条件下字典序最小的序列。给出 m(2e5)组询问,每一组询问给出一个 k i k_i p o s i pos_i ,要求回答长度为 k i k_i 最佳子序列的第 p o s i pos_i 个元素大小是多少。

思路:因为我们不可能处理出所有长度的最佳子序列的所有位置的元素,所以考虑对询问离线。要满足元素和最大并且字典序最小,我们可以对每个元素建一个二元组 ( a i , i ) (a_i,i) ,然后第一关键字降序,第二关键字升序排列,这样前 k 个数就是长度为 k 的最佳子序列要取的数(和位置),因为这既保证了和最大,也保证了优先选择 index 更小的,也就是字典序最小。

然后就可以从小到大遍历 k 了,我们还需要维护一个 Treap,用来保存已经选取的元素的 index,遍历 k 的时候同时处理长度为 k 的所有询问(每个询问就是查询一次 Treap 中的某个排名的元素)就可以了。复杂度为 O(nlogn)。

代码

struct treap
{
    int tot,root;
    int *t,*num,(*ch)[2],*rd,*siz;

    treap(int maxn)
    {
        t=new int[maxn](); num=new int[maxn]();
        rd=new int[maxn](); siz=new int[maxn]();
        ch=new int[maxn][2](); root=tot=0;
    }

    void push_up(int k) {siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+num[k];}

    void rotate(int &k,int d)
    {
        int x=ch[k][d^1];
        ch[k][d^1]=ch[x][d]; ch[x][d]=k;
        push_up(k); push_up(x);
        k=x;
    }

    void insert(int &k,int x)
    {
        if(!k) {k=++tot; t[k]=x; num[k]=siz[k]=1; rd[k]=rand(); return;}
        else if(t[k]==x) {num[k]++; siz[k]++; return;}
        else
        {
            int d=x>t[k];
            insert(ch[k][d],x);
            if(rd[k]<rd[ch[k][d]]) rotate(k,d^1);
            push_up(k);
        }
    }
    void insert(int x) {insert(root,x);}

    int who_ranking(int k,int r)
    {
        if(!k) return 0;
        if(r<=siz[ch[k][0]]) return who_ranking(ch[k][0],r);
        if(r>siz[ch[k][0]]+num[k]) return who_ranking(ch[k][1],r-siz[ch[k][0]]-num[k]);
        return t[k];
    }
    int who_ranking(int r) {return who_ranking(root,r);}
};

const int maxn=2e5+5;
int n,a[maxn],k[maxn],p[maxn],ans[maxn];
struct node
{
    int value,id;
    bool operator < (const node x) const
    {
        if(value==x.value) return id<x.id;
        return value>x.value;
    }
}b[maxn];
vector<node> q[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    n=read();
    treap t(n+5);
    REP(i,1,n) a[i]=read(),b[i]=(node){a[i],i};
    sort(b+1,b+n+1);
    int m=read();
    REP(i,1,m) k[i]=read(),p[i]=read(),q[k[i]].push_back((node){p[i],i});
    REP(i,1,n)
    {
        t.insert(b[i].id);
        REP(j,0,q[i].size()-1)
        {
            int w=q[i][j].value,id=q[i][j].id;
            ans[id]=a[t.who_ranking(w)];
        }
    }
    REP(i,1,m) printf("%d\n",ans[i]);

    return 0;
}



E Arson In Berland Forest

题意:(略去背景)有一张 n*m(n*m<1e6)的地图,上面有些格点为 1,有些为 0;初始时刻为 0,每经过 1 时间,值为 1 的格点会把八个方向相邻的格点也变成 1。现在要求一个初始地图(给每个格点分配 1 或 0),使得经过时间 T,该初始地图会变成给出的地图,并且时间 T 要尽可能大。

思路:可以看出这道题的意思就是要找出最大的 k(k为奇数),使得给出的地图中每个为 1 的格点都能被某个 k*k 且全部都为 1 的正方形覆盖。首先对给出的地图求一个前缀和 s,然后我们设 fs(i, j, k) = s[i][j] - s[i-k][j] - s[i][j-k] + s[i-k][j-k](也就是以 (i, j) 为右下角,边长为 k 的正方形的权值和),考虑二分答案,对于某个边长 k,对于某个值为 1 的格点 (i, j) ,它能被上述的某个正方形覆盖,当且仅当在正方形区间 [i ~ i+k-1, j ~ j+k-1] 存在一个格点 (x, y) ,使得 fs(x, y, k) = k*k 。

这样考虑之后,我们就可以遍历数组求解了,还有要注意的就是找 (x, y) 时要更加优先更右下角的,这样可以在找一次的时候能标记更多的格点。这种做法复杂度 O(n*m*log(min(n,m)))。

代码

int **create(int n,int m)
{
    int **a=new int*[n+5];
    REP(i,0,n+1) a[i]=new int[m+5];
    REP(i,0,n+1) REP(j,0,m+1) a[i][j]=0;
    return a;
}

const int maxn=1e6+5;
char ss[maxn];
int **a,**s,n,m;

int get(int x,int y,int k)
{
    if(x<k || y<k || x>n || y>m) return 0;
    return s[x][y]-s[x][y-k]-s[x-k][y]+s[x-k][y-k];
}

bool can(int k)
{
    int **temp=create(n,m);
    REP(i,1,n) REP(j,1,m) if(!temp[i][j] && a[i][j])
    {
        int ii=0,jj=0;
        REP(x,i,min(i+k-1,n))
        {
            REP(y,j,min(j+k-1,m))
                if(get(x,y,k)==k*k)
                {
                    ii=x; jj=y;
                }
        }
        if(!ii || !jj) return 0;
        REP(x,ii-k+1,ii) REP(y,jj-k+1,jj)
        {
            if(!a[x][y]) return 0;
            temp[x][y]=1;
        }
    }
    return 1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read(),m=read();
    a=create(n,m),s=create(n,m);
    REP(i,1,n)
    {
        scanf("%s",ss+1);
        REP(j,1,m) a[i][j]=(ss[j]=='X');
    }
    REP(i,1,n) REP(j,1,m) s[i][j]=s[i][j-1]+s[i-1][j]+a[i][j]-s[i-1][j-1];

    int l=0,r=(min(n,m)-1)/2+1,mid;
    while(l<r-1)
    {
        mid=(l+r)>>1;
        if(can(mid*2+1)) l=mid;
        else r=mid;
    }
    printf("%d\n",l);
    int k=l*2+1;
    REP(i,1,n)
    {
        REP(j,1,m) putchar(get(i+l,j+l,k)==k*k?'X':'.');
        puts("");
    }

    return 0;
}



F Wrong Answer on test 233

题意:有n道题目(编号1-n),每道题目有k个可能的选项,但是只有 h i h_i 是正确答案,每道题目一分。总共肯定有 k n k^n 中可能的答案序列,问有多少种答案序列a满足:将这个答案序列向右平移1之后(也就是原本答案a[i]现在拿去回答第 i%n+1 个问题),得到的分数比平移前高。

思路:如果 h [ i ] = h [ i % n + 1 ] h[i]=h[i\%n+1] ,那么就不用考虑第 i 个问题了,因为无论怎样回答平移前后都是一样的结果。假设 h [ i ] h [ i % n + 1 ] h[i] \neq h[i\%n+1] 的数目为 t,我们考虑计算平移前后分数不变的序列数,因为分数变多和变少是对称的,所以计算不变的数目 x,最终答案就是 ( k n x ) / 2 (k^n-x)/2 。其它 n-t 个位置给 x 的贡献是累乘 k n t k^{n-t} ,t 个位置中,我们枚举 h[i]==a[i] 的数目q,很容易得出对于 q 的方案数为 C t q C t q q ( k 2 ) t 2 q C_t^qC_{t-q}^q(k-2)^{t-2q} ,所以最终 x = k n t q = 0 t 2 ( C t q C t q q ( k 2 ) t 2 q ) x=k^{n-t} \sum_{q=0}^{\lfloor \frac{t}{2}\rfloor}(C_t^qC_{t-q}^q(k-2)^{t-2q}) ,答案为 ( k n x ) / 2 (k^n-x)/2

另外题解是直接计算的,也可以,不过会麻烦一点点(要分奇偶讨论)

代码

const int M=998244353;
const int maxn=2e5+5;
int n,k,jie[maxn],h[maxn],t;
 
int ksm(int x,int k)
{
    int ret=1;
    while(k)
    {
        if(k&1) ret=1ll*ret*x%M;
        x=1ll*x*x%M;
        k>>=1;
    }
    return ret;
}
 
int C(int n,int m)
{
    if(!m || m>=n) return 1;
    int ret=jie[n],temp=1ll*jie[m]*jie[n-m]%M;
    ret=1ll*ret*ksm(temp,M-2)%M;
    return ret;
}
 
int main()
{
    //freopen("input.txt","r",stdin);
    n=read(),k=read();
    REP(i,1,n) h[i]=read();
    REP(i,1,n) if(h[i]!=h[i%n+1]) t++;
    jie[1]=1;
    REP(i,2,n) jie[i]=1ll*jie[i-1]*i%M;
    int ans=ksm(k,n-t),temp=0;
    REP(q,0,t>>1)
    {
        int x=1ll*C(t,q)*C(t-q,q)%M*ksm(k-2,t-2*q)%M;
        temp=(temp+x)%M;
    }
    ans=1ll*ans*temp%M;
    temp=ksm(k,n);
    cout<<1ll*(1ll*temp-ans+M)*ksm(2,M-2)%M;
 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dragonylee/article/details/105921394