NOIP 模拟 1003

天空龙

奥西里斯有a 个红色,b 个黄色,c 个蓝色,他想用画出最好的画,可是需要至少x 个红色,y 个黄色和z 个蓝色,似乎并不够。别担心,奥西里斯会魔法!他可以把任何两个同种颜色转化为一个另一种颜色!请问他能不能完成呢?

t<=100,0<=a,b,c,x,y,z<=1000000。

题解

稍微想一下就知道不存在拿对自己有用的去帮助别人,最后让别人帮自己,那么直接看多出来的能填多少空就行。

 

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

int t,a,b,c,x,y,z;

int main(){
    freopen("osiris.in","r",stdin);
    freopen("osiris.out","w",stdout);
    scanf("%d",&t);
    while(t--){
        int tot=0,need=0;
        scanf("%d%d%d%d%d%d",&a,&b,&c,&x,&y,&z);
        if(a>x) tot+=(a-x)>>1;
        else need+=x-a;
        if(b>y) tot+=(b-y)>>1;
        else need+=y-b;
        if(c>z) tot+=(c-z)>>1;
        else need+=z-c;
        printf("%s\n",tot>=need ? "YES" : "NO");
    }
}
/*
3
4 4 0 2 1 2
5 6 1 2 7 2
3 3 3 2 2 2
*/
osiris

 


巨神兵

欧贝利斯克的巨神兵很喜欢有向图,有一天他找到了一张n 个点m 条边的有向图。

欧贝利斯克认为一个没有环的有向图是优美的,请问这张图有多少个子图(即选定一个边集)是优美的?答案对1,000,000,007 取模。

对于40%的数据n<=5,m<=20;

对于60%的数据n<=10;

对于80%的数据n<=15;

对于100%的数据n<=17。

题解

40%的做法,就暴力枚举边集最后用拓扑判环。

#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=1000000007;
const int maxn=20;
const int maxm=250;
int n,m,ret;
int cnt,du[maxn],d[maxn];
vector<int> c[maxn];
struct edge{
    int x,y;
}e[maxm];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

int topsort(){
    queue<int> q;
    int tot=0;
    for(int i=1;i<=n;i++){
        d[i]=du[i];
        if(!du[i]) q.push(i),tot++;
    }
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(unsigned int i=0;i<c[x].size();i++){
            int y=c[x][i];
            if(d[y]){
                d[y]--;
                if(!d[y]) q.push(y),tot++;
            }
        }
    }
    return tot==n;
}

void dfs(int s){
    if(!topsort()) return ;
    if(s>m){
        ret++;
        if(ret>=mod) ret-=mod;
        return ;
  }
    dfs(s+1);
    du[e[s].y]++;
    c[e[s].x].push_back(e[s].y);
    dfs(s+1);
    c[e[s].x].pop_back();
    du[e[s].y]--;
}

int main(){
    freopen("obelisk.in","r",stdin);
    freopen("obelisk.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=m;i++) read(e[i].x),read(e[i].y);
    dfs(1);
    printf("%d",ret);
}
obelisk


对于60%的数据,考虑把有向图分层(貌似是一种套路),分层就是类似topsort一样的,第一层是最初入度为0的点,去掉他们入度为0的是第二层....

然后考虑f[i][j],i为现在选取的点集,j为最后一层的点集,考虑一个可以转移的点集k:满足与i无交集,k中每个点都与j有连边(和i其他点无所谓)

考虑转移答案f[i|k]+=f[i][j]*num1,num1为选边的方案数,是每个k中的点选边方案数的乘积,k中点选边方案数是至少选一条连向j的边*选取连向i中其他点的边的方案数。

#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int mod=1000000007;
const int maxn=100;
const int maxm=250;
int n,m,tot;
int cnt,head[maxn];
ll ret,pow2[maxn],f[1030][1030];
struct edge{
    int x,y,next;
}e[maxm];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void add(int x,int y){
    e[++cnt]=(edge){x,y,head[x]};
    head[x]=cnt;
}

bool get(int x,int s){return (1<<(x-1))&s;}

int main(){
    freopen("obelisk.in","r",stdin);
    freopen("obelisk.out","w",stdout);
    read(n);read(m);
    pow2[0]=1;
    for(int i=1;i<=m;i++) pow2[i]=pow2[i-1]*2;
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        add(y,x);
    }
    tot=(1<<n)-1;
    for(int i=1;i<=tot;i++) f[i][i]=1;
    for(int i=1;i<=tot;i++)//总共选的点 
        for(int j=i;j;j=(j-1)&i){//最后一层,是i的子集
        if(!f[i][j]) continue;
            int cx=tot^i;//i的补集,k的选取集合 
            for(int k=cx;k;k=(k-1)&cx){
                ll num1=1;
                for(int o=1;o<=n;o++){
                    if(!get(o,k)) continue;//找出k里面的节点
                    ll cnt1=0,cnt2=0;//连向j的边,连向i中除了j的边 
                    for(int l=head[o];l;l=e[l].next){
                        int y=e[l].y;
                        if(get(y,j)) cnt1++;
                        else if(get(y,i)) cnt2++;
                    }
                    num1=num1*(pow2[cnt1]-1)%mod;//必须选一个 
                    num1=num1*pow2[cnt2]%mod;
                    if(!num1) break;//只能是与j无连边 
                }
                if(!num1) continue;
                f[i|k][k]=(f[i|k][k]+f[i][j]*num1%mod)%mod;
            }
        }
    for(int i=1;i<=tot;i++) ret=(ret+f[tot][i])%mod;
    printf("%d",ret);
}
60

对于100%的数据,考虑不要第二维,会统计重复所以加个容斥(我也不知道为啥容斥是这样)

具体看代码吧,理解到上面的就差不多可以理解了(除了容斥)

#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int mod=1000000007;
const int maxn=20;
const int maxm=250;
int n,m,tot;
int mp[maxn][maxn];
ll pow2[maxm],f[1<<17];
ll a[1<<17],b[1<<17];//边数 
int num[1<<17];//容斥系数,按二进制下1的个数 

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

bool get(int x,int s){return (1<<(x-1))&s;}

int main(){
    freopen("obelisk.in","r",stdin);
    freopen("obelisk.out","w",stdout);
    read(n);read(m);
    pow2[0]=1;
    for(int i=1;i<=m;i++) pow2[i]=pow2[i-1]*2%mod;
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        mp[x-1][y-1]=1;
    }
    tot=(1<<n)-1;
    num[0]=-1;
    for(int i=1;i<=tot;i++) num[i]=num[i>>1]*( i&1 ? -1 : 1 );
    f[0]=1;
    for(int i=0;i<tot;i++){
        for(int j=0;j<n;j++) b[1<<j]=0;
        for(int j=0;j<n;j++)
         if((1<<j)&i)
          for(int k=0;k<n;k++)
           b[1<<k]+=mp[j][k];
        a[0]=0;
        int j=tot^i;
        for(int k=(j-1)&j;;k=(k-1)&j){
            int now=j^k,o=now&-now;//为了从小到大枚举
            a[now]=a[now^o]+b[o];
            f[i|now]=(f[i|now]+f[i]*pow2[a[now]]*num[now])%mod;
            if(!k) break;
        }
    }
    printf("%d",(f[tot]+mod)%mod);
}
100

 

 

 


 

太阳神

太阳神拉很喜欢最小公倍数,有一天他想到了一个关于最小公倍数的题目。

求满足如下条件的数对(a,b)对数:a,b 均为正整数且a,b<=n 而lcm(a,b)>n。其中的lcm 当然表示最小公倍数。答案对1,000,000,007取模

对于100%的数据n<=10000000000。

题解

不会数论,只有照着别人说

有杜教筛啥的,不过我这就讲我理解得到的

补集转换(顶不住)

那么就考虑lcm(i,j)<=n的

$\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(i,j)\leq n]$

$\sum_{k}\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(\frac{i}{k},\frac{j}{k})=1][\frac{ij}{k}\leq n]$

$\sum_{k}\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{d|gcd(\frac{i}{k},\frac{j}{k})}\mu (d)[\frac{ij}{k}\leq n]$

$\sum_{k}\mu (d)\sum_{d|\frac{i}{k}}^{n}\sum_{d|\frac{j}{k}}^{n}[\frac{ij}{k}\leq n]$

$\sum_{k}\mu (d)[\frac{ij}{k}\leq \frac{n}{d^2}]$

$\mu (d)\sum_{k}[\frac{i}{k}\frac{j}{k}k\leq \frac{n}{d^2}]$

$\frac{n}{d^2}\geq 1,\therefore d\leq \sqrt n$

所以考虑枚举d,找出符合的$\frac{i}{k},\frac{j}{k},k$

$假设\frac{i}{k}\leq \frac{j}{k}\leq k$

$\frac{i}{k}\leq \sqrt[3]{\frac{n}{d^2}},\frac{j}{k}*\frac{j}{k}\leq\frac{n}{d^2i}$

所以就枚举前两个可以得到k的范围,就知道满足条件的有多少个了,然后考虑三者可以互换(因为本身没有大小关系),分类讨论一下即可。

最后答案是n*n-求出的答案

 

#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int mod=1000000007;
const int maxn=100000;
ll n,m,ans;
int prime[maxn+5],mu[maxn+5];
bool not_prime[maxn+5];

void init(){
    mu[1]=1;
    for(int i=2;i<=maxn;i++){
        if(!not_prime[i]){
            prime[++prime[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=prime[0]&&i*prime[j]<=maxn;j++){
            not_prime[i*prime[j]]=true;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i];
            else {
                mu[i*prime[j]]=0;
                break;
            }
        }
    }
}

int main(){
    freopen("ra.in","r",stdin);
    freopen("ra.out","w",stdout);
    scanf("%lld",&n);
    init();
    m=sqrt(n+0.5);
    for(int d=1;d<=m;d++){
        ll val=n/d/d,ret=0;
        for(ll i=1;i*i*i<=val;i++)
         for(ll j=i;j*j<=val/i;j++){
              ll c=val/i/j-j+1;
              //i*j*k<=val,i<=j<=k
              //c:k的取值个数
              if(i==j) ret=(ret+1+(c-1)*3)%mod;//1:j==k,否则i,j,k有三种排列
             else ret=(ret+3+(c-1)*6)%mod; 
         }
        ans=(ans+mu[d]*ret)%mod;
    }
    ans=(n%mod)*(n%mod)%mod-ans;
    ans=(ans%mod+mod)%mod;
    printf("%lld",ans);
}
ra

 

posted @ 2019-10-03 20:35  _JSQ  阅读(298)  评论(0编辑  收藏  举报