9.25 正睿NOIP模拟题解

T1

大水题,先判是否有解,再排序即可。代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

char s[N];
int len,all=0,a[N];

inline bool cmp(int a,int b){return a>b;}

int main(){
    scanf("%s",s+1);len=strlen(s+1);
    for(int i=1;i<=len;i++){
        all+=s[i]-'0';a[i]=s[i]-'0';
    }
    sort(a+1,a+len+1,cmp);
    if(all%3!=0||a[len]!=0){return puts("-1"),0;}
    for(int i=1;i<=len;i++) printf("%d",a[i]);
    return 0;
}

T2

不难发现,对于一个 \(x\) 排列来说,如果它存在,那么这个排列的两边必定都是大于 \(x\) 的,这提示我们用双指针来做,看删掉的数的个数是否符合其应该删掉的数的个数。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 10010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,a[N],cnt=0;
bool ans[N];

int main(){
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    int l=1,r=n;
    for(int i=n;i>=1;i--){
        while(a[l]>i){l++;cnt++;}
        while(a[r]>i){r--;cnt++;}
        if(cnt==n-i){ans[i]=1;}
    }
    for(int i=1;i<=n;i++) printf("%d",ans[i]);
    return 0;
}

T3

一道构造题,并不是很难,但是考场上因为错误的大方向导致没有想出正解。

一种很常见的思路是贪心来做,每次取目前连续段最少的扩展,但这样有问题,不难发现,一张足够大的竞赛图可以把这个东西卡掉。不过这张竞赛图是一定有解的。在原来的 dp 数组上,我们可以通过更改之前边的颜色来使得有正解。所以这就让我们有这样的一个思路,就是我们先涂上一种颜色,等到实在不行了,我们再换另一种颜色。

基于这样一种思想,我们分组来做。

首先我们把点拓扑排序,然后我们每 \(42\) 个点分一个小组,每 \(42\) 个小组里面分一个大组,小组里面的连边我们用 \(B\),小组与小组之间的连边我们用 \(R\),大组与大组之间的连边我们用 \(G\)。不难发现,因为 \(43^3>50000\),所以大组的数量不会超过 \(42\) 个。因为原图是一种 DAG,容易发现,这样构造是一定没有问题的。

注意,必须需要拓扑序来保证正确性,原因在于如果没有拓扑序,组与组之间的连边没有定向,这样可能会产生一条长度为 \(84\) 的长链。但是如果边都是定向,就没有问题。

这个题的切入点一个在于大方向的分析,另一个在于 \(42\) 这个数字的关系。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 500100
#define M 400010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

int n,m,rd[N],ans[N];

struct edge{
    int to,next,id,col;
    inline void Init(int to_,int ne_,int id_){
        to=to_;next=ne_;id=id_;
    }
}li[M];
int tail,head[N];

inline void Add(int from,int to,int id){
    li[++tail].Init(to,head[from],id);
    head[from]=tail;
}

int TopOrder[N],tt;

queue<int> q;
inline void Bfs(){
    while(q.size()){
        int top=q.front();q.pop();
        TopOrder[++tt]=top;
        for(int x=head[top];x;x=li[x].next){
            int to=li[x].to;rd[to]--;
            if(!rd[to]) q.push(to);
        }
    }
}

int Small[N],Big[N];

inline void PreWork(){
    for(int i=1;i<=n;i+=42){
        for(int j=i;j<=i+41&&j<=n;j++) Small[TopOrder[j]]=(i-1)/42+1;
    }
    for(int i=1;i<=n;i+=1764){
        for(int j=i;j<=i+1763&&j<=n;j++) Big[TopOrder[j]]=(i-1)/1764+1;
    }
}

int main(){
    memset(ans,-1,sizeof(ans));
    read(n);read(m);
    for(int i=1;i<=m;i++){
        int from,to;read(from);read(to);
        Add(from,to,i);rd[to]++;
    }
    for(int i=1;i<=n;i++) if(!rd[i]) q.push(i);
    Bfs();PreWork();
    for(int i=1;i<=n;i++){
        for(int x=head[i];x;x=li[x].next){
            int to=li[x].to;
            if(Big[i]!=Big[to]){
                ans[li[x].id]=2;
            }
            else if(Small[i]!=Small[to]) ans[li[x].id]=1;
            else ans[li[x].id]=0;
        }
    }
    for(int i=1;i<=m;i++){
        if(ans[i]==0) puts("R");
        else if(ans[i]==1) puts("G");
        else puts("B");
    }
    return 0;
}

T4

谢老板的题又打麻烦了,传统艺能。

因为谢老板的思路我打了 \(150\) 行还没有打完,所以这里我说一下另一种做法。

我们考虑一条线段没有相交也就是说以这个线段为对角线的所有四边形都是凹四边形。所以我们统计一下所有四边形数量和所有凹四边形数量就可以。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 4010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;
const dd pi=acos(-1);

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n;
pair<double,int> b[N<<1];
pair<int,int> p[N<<1];
int c[N][N][2],tail,ans,s[N<<1];

signed main(){
    read(n);
    for(int i=1;i<=n;i++){read(p[i].first);read(p[i].second);}
    for(int i=1;i<=n;i++){
        tail=0;
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            b[++tail]=make_pair(atan2(p[j].second-p[i].second,p[j].first-p[i].first),j);
        }
        sort(b+1,b+tail+1);
        for(int i=1;i<=tail;i++){b[i+tail]=b[i];b[i+tail].first+=2*pi;}
        for(int j=1,k=1;j<=tail;j++){
            while(k+1<=2*tail&&b[k+1].first-b[j].first<pi) k++;
            s[j]=s[j+tail]=k-j;c[i][b[j].second][0]=s[j]*(tail-s[j]-1);
        }
        for(int j=1;j<=2*tail;j++) s[j]+=s[j-1];
        for(int j=1,k=1;j<=tail;j++){
            while(k+1<=2*tail&&b[k+1].first-b[j].first<pi) k++;
            c[i][b[j].second][1]=s[k]-s[j]-(k-j)*(k-j-1)/2;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(c[i][j][0]==c[i][j][1]+c[j][i][1]) ans++;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

以上的代码中,\(c_{i,j,0/1}\) 表示以 \((i,j)\) 为对角线的 全部的四边形数量和角 \(i\) 大于 \(180\) 度的四边形数量,\(39\) 到 \(42\) 行比较好容易理解,这里来说一下 \(44\) 到 \(47\) 行是怎么求的。通过画图,我们发现 \(i,j,k'\) 和在 \(s_{k'}\) 的点能够构成凹四边形,除了在 \(j\) 到 \(k\) 之间的点是多余的。画图有助于理解,光说是很难说清楚地。

上一篇:洛谷 P1799 数列(dp)


下一篇:list操作