2019年5,6月博客汇总

[USACO12FEB]牛的IDCow IDs

显然用的位数越多能表示出的数就越多,在n个位数中选择m个位数为1的方案数明显为\(C_n^m\)。我们从最高位向下进行考虑,我们可以从小往大枚举选择的位数。如过\(C_i^k\)大于n且\(C_{i-1}^k\)时,显然在k-1位怎么放都无法满足要求,所以最高位为i。我们依次向下确定,则接下来要放置k-1位的第\(n-C_{i-1}^k\)位。依次类推即可。(中间可以二分以降低复杂度,但是暴力就能过)

这题预处理C会比较方便

代码

#include<cstdio>
using namespace std;
namespace orz{
const int N=100005;
const long long MAX=100000000;
long long C[N][12];
int ans[12]; 
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void showC(){
    for(int i=0;i<=20;++i){
        for(int j=0;j<=12;++j){
            if(!C[i][j]){
                putchar('\n');
                break;
            }
            printf("%lld ",C[i][j]);
        }
    }   
}
int QAQ(){
    freopen("3048.in","r",stdin);
    int n,k;
    n=read();k=read();
    if(k==1){
        putchar('1');
        for(int i=1;i<n;++i)
            putchar('0');
        return false;
    }
    C[0][0]=1;
    for(int i=1,j=1;i<N;++i){
        C[i][0]=1;
        for(j=1;j<=k;++j){
            C[i][j]=C[i-1][j]+C[i-1][j-1];
            if(C[i][j]>MAX||C[i][j]==0)break;
        }
        if(C[i][j]>MAX||C[i][j]==0)continue;
    }
    for(int i=k;i;--i){
        for(int j=1;;++j){
            if(C[j][i]>=n){//如果放置数大于n 
                ans[i]=j;
                n-=C[j-1][i];
                break;
            }
        }
    }
    for(int i=ans[k],cnt=k;i;--i){
        if(i==ans[cnt]){
            putchar('1');
            --cnt;
        }
        else{
            putchar('0');
        }
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

2019普通高等OIer全团队统一考试

movie


首先这个游戏是绝对公平的,所以它的概率和id并没有什么关系。
所以就是\(\frac{k}{n}\)

代码

#include<cstdio>
using namespace std;
namespace orz{
inline long long read(){
    long long a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline long long gcd(long long a,long long b){
    return b?gcd(b,a%b):a;
}
int QAQ(){
    long long n,k,id;
    n=read(),k=read(),id=read();
    if(n<=k){
        printf("1/1");
        return false;
    }
    if(k==0){
        printf("0/1");
        return false;
    }
    printf("%lld/%lld",k/gcd(k,n),n/gcd(k,n));
    return false;
}
}
int main(){
    return orz::QAQ();
}

tower



乍一看是一个数字三角形,实际上就是一个数字三角形。
我们从上和下分别跑一次dp两个数值加起来减去当前值就可以得到必须经过当前点的答案。
对于每一个询问,我们只需要查询除了这个点之外其他点答案中的最大值就可以了。

那怎么查呢?
如果暴力,复杂度会很高,然而G老师就这么过了
我们可以建一棵线段树,每次查询是logn的。
我们仔细考虑发现除了最大值那个点的答案是次大值,其他点的答案都是最大值。
所以我们求出最大值次大值就可以了。

线段树代码

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=510500;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
struct SegmentTree{
    int l,r,mx;
}t[(N<<2)+1];
int s[N],top[N],bottom[N],c[N];
int n,m;
inline int num(int x,int y){
    return (x-1)*x/2+y;
}
int query(int p,int l,int r){
    if(t[p].l==l&&t[p].r==r)return t[p].mx;
    int mid=(t[p].l+t[p].r)>>1;
    if(r<=mid)return query(p<<1,l,r);
    else if(l>mid)return query(p<<1|1,l,r);
    else return max(query(p<<1,l,mid),query(p<<1|1,mid+1,r));
}
inline int ask(int x,int y){
    if(x==1&&y==1)return -1;
    int ans=-100000;
    if(y^1)ans=max(ans,query(1,num(x,1),num(x,y-1)));
    if(y^x)ans=max(ans,query(1,num(x,y+1),num(x,x)));
    return ans;
}
void build(int p,int l,int r){
    t[p].l=l;t[p].r=r;
    if(l==r){
        t[p].mx=top[l]+bottom[l]-s[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    t[p].mx=max(t[p<<1].mx,t[p<<1|1].mx);
}
inline void dp(){
    top[num(1,1)]=s[num(1,1)];
    for(int i=2;i<=n;++i)
        top[num(i,1)]=top[num(i-1,1)]+s[num(i,1)],
        top[num(i,i)]=top[num(i-1,i-1)]+s[num(i,i)];
    for(int i=2;i<=n;++i)
        for(int j=2;j<i;++j)
            top[num(i,j)]=max(top[num(i-1,j)],top[num(i-1,j-1)])+s[num(i,j)];
    for(int i=1;i<=n;++i)
        bottom[num(n,i)]=s[num(n,i)];
    for(int i=n-1;i;--i)
        for(int j=1;j<=i;++j)
            bottom[num(i,j)]=max(bottom[num(i+1,j)],bottom[num(i+1,j+1)])+s[num(i,j)];
    build(1,1,num(n,n));
}
int QAQ(){
//      freopen("tower.in","r",stdin);
    int x,y;
    n=read(),m=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            s[num(i,j)]=read();
    dp();
    while(m--){
        x=read(),y=read();
        printf("%d\n",ask(x,y));
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

正解代码

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=510500;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int s[N],top[N],bottom[N],ans[N],c[N];
int mx[1002];
int n,m;
inline int num(int x,int y){
    return (x-1)*x/2+y;
}
inline void dp(){
    top[num(1,1)]=s[num(1,1)];
    for(int i=2;i<=n;++i)
        top[num(i,1)]=top[num(i-1,1)]+s[num(i,1)],
        top[num(i,i)]=top[num(i-1,i-1)]+s[num(i,i)];
    for(int i=2;i<=n;++i)
        for(int j=2;j<i;++j)
            top[num(i,j)]=max(top[num(i-1,j)],top[num(i-1,j-1)])+s[num(i,j)];
    for(int i=1;i<=n;++i)
        bottom[num(n,i)]=s[num(n,i)];
    for(int i=n-1;i;--i)
        for(int j=1;j<=i;++j)
            bottom[num(i,j)]=max(bottom[num(i+1,j)],bottom[num(i+1,j+1)])+s[num(i,j)];
    for(int i=1;i<=num(n,n);++i)
        c[i]=top[i]+bottom[i]-s[i];
    for(int i=1;i<=n;++i){
        int t=-1;
        for(int j=1;j<=i;++j)
            if(c[num(i,j)]>t){
                t=c[num(i,j)];
                mx[i]=j;
            }
    }
    for(int i=1;i<=n;++i){
        int t=-1;
        for(int j=1;j<=i;++j)
            if(j!=mx[i]){
                t=max(t,c[num(i,j)]);
                ans[num(i,j)]=c[num(i,mx[i])];
            }
        ans[num(i,mx[i])]=t;
    }
}
int QAQ(){
//  freopen("tower.in","r",stdin);
    int x,y;
    n=read(),m=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            s[num(i,j)]=read();
    dp();
    while(m--){
        x=read(),y=read();
        printf("%d\n",ans[num(x,y)]);
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

segmenttree  




直接暴力,得分50。    

动态开点的暴力,得分75。    

说实话这题暴力分有点多。
线段树也是一棵树,所以我们应该从树的角度来考虑。
对于一个区间,它最差的区间定位是每一个叶子一个区间。
然而如果有一个爸爸的话答案就会减少1。设这个区间一共有t个完全被包含的区间。
长度为k。
于是有t-k个爸爸。
所以答案为\(k-(t-k)=2k-t\)

代码

#include<cstdio>
#include<algorithm>
#include<vector>
#define IT vector<ques>::iterator
using namespace std;
namespace orz{
const int N=500000;
int c[N];
int n,m;
int ans[N],len[N];
int tot=0;
struct node{
    int l,r;
    bool operator<(const node &a)const{
        return this->l<a.l;
    }
}a[N];
struct ques{
    int l,r,t,id;
    ques(int a,int b,int c,int d){
        l=a,r=b,t=c,id=d;
    }
};
vector<ques>q[N];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void add(int x,int k){
    for(;x<=n;x+=x&-x)
        c[x]+=k;
}
inline int ask(int x){
    int ans=0;
    for(;x;x-=x&-x)
        ans+=c[x];
    return ans;
}
void build(int l,int r){
    a[++tot].l=l,a[tot].r=r;
    if(l==r)return ;
    int mid=read();
    build(l,mid),build(mid+1,r);
}
int QAQ(){
//      freopen("segment.in","r",stdin);
    n=read(),m=read();
    int x,y;
    build(1,n);
    sort(a+1,a+tot+1);
    for(int i=1;i<=m;++i){
        x=read(),y=read();
        len[i]=y-x+1;
        q[x-1].push_back(ques(x,y,-1,i));
        q[y].push_back(ques(x,y,1,i));
    }
    int cwy=1;
    for(int i=1;i<=n;++i){
        while(a[cwy].l==i){
            add(a[cwy].r,1);
            ++cwy;
        }
        for(IT j=q[i].begin();j!=q[i].end();++j)
            ans[j->id]+=(ask(j->r)-ask(j->l-1))*j->t;
    }
    for(int i=1;i<=m;++i)
        printf("%d\n",len[i]*2-ans[i]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

20190610爆零赛

爆零了QAQ,自闭了。

math


我们看到\((-1)^n\)的形式可以想到和奇偶性有关,我们考虑约数个数的公式\((c_1+1)(c_2+1)(c_3+1)...(c_n+1)\)。我们会发现其中只要有一个数是偶数整个式子就是偶数,我们考虑它的值为奇数的情况,则每一个\(c_i\)都为偶数,即这个数为一个完全平方数。
所以问题也就变为了对于每一个i,有多少个\(ij\)为完全平方数。
对于一个数来说我们先把它所有指数为奇数的项补为偶数,这显然是它所能乘出的第一个平方数。然后我们对它成对的补指数一定也是个平方数。写到一半开开告诉我这么推不出来。
我认真研读了题解。
正解思路和这个类似,我们把这个数的每一个指数为奇数的项提出一个来,这个数i可以表示为\(p\*q^2_1\),满足条件的j可以表示为\(p\*q^2_2\),所以答案就为\(\sqrt{\frac{m}{p}}\)向下取整,相当于先把p选出来后在去数q。
所以我们只要把每一个i的p搞出来就可以了。
我们来线性筛,记录一个ans数组,在线性筛中我们把这个数用i×prime[j]表示,我们判断ans[i]里面有没有prime[j]就可以了。
这次各种题都卡常233。

代码

#include<cstdio>
#include<cmath>
using namespace std;
namespace orz{
const int N=10000100;
inline long long read(){
    long long a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int mindiv[N],prime[N],ans[N],tot=0;
int QAQ(){
    freopen("math.in","r",stdin);
    freopen("math.out","w",stdout);
    int n=read();
    long long m=read();
    register int k;
    int question=0;
    ans[1]=1;
    register int j;
    for(register int i=2;i<=n;++i){
        if(!mindiv[i])prime[++tot]=mindiv[i]=ans[i]=i;
        for(j=1;j<=tot&&((k=i*prime[j])<=n)&&prime[j]<=mindiv[i];++j){
            mindiv[k]=prime[j];
            if(ans[i]%prime[j]==0)ans[k]=ans[i]/prime[j];
            else ans[k]=ans[i]*prime[j];
        }
    }
    for(register int i=1;i<=n;++i)
        question+=(int)sqrt(m/ans[i])&1?-1:1;
    printf("%d",question);
    return false;
}
}
int main(){
    return orz::QAQ();
}

osu


这题乍一看是个DP,直接DP的话是个\(O(n^3)\)的大暴力,所以肯定是过不了的,蒟蒻因为写的太丑连暴力分都没拿够。
我们考虑二分,虽然原题中值的形式比较诡异,但是总共就\(n^2\)个边权,都跑出来二分也可以。二分之后跑个最长路来check。
因为原图是个DAG,所以最长路可以dp来求,最终复杂度为\(O(n^2logn^2)\)

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
namespace orz{
const int N=2022;
int cnt=0;
int id[N][N];
int n,k;
int sx[((N*N)>>1)+1000];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
struct num{
    long long a,b,c;
    inline long long gcd(int a,int b){
        return b?gcd(b,a%b):a;  
    }
    bool operator<(const num&b)const{
        return this->a*this->a*this->b*b.c*b.c<b.a*b.a*b.b*this->c*this->c;
    }
    void print(void){
        int t;
        t=sqrt(b);
        for(int i=t;i;--i){
            if(b%(i*i)==0){
                b/=i*i;
                a*=i;
            }
        }
        t=gcd(a,c);
        a/=t;
        c/=t;
        printf("%lld %lld %lld",a,b,c);
    }
};
struct node{
    int id;
    num v;
    bool operator< (const node &a)const {
        return this->v<a.v;
    }
}b[((N*N)>>1)+1000];
struct point{
    int x,y,t;
}a[N];
inline num getdis(int x,int y){
    num res;
    res.b=(a[x].x-a[y].x)*(a[x].x-a[y].x)+(a[x].y-a[y].y)*(a[x].y-a[y].y);
    res.c=a[y].t-a[x].t;
    res.a=1;
    return res;
}
int f[N];
inline bool check(int x){
    for(int i=1;i<=n;++i)
        f[i]=0;
    for(int i=0;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            if(sx[id[i][j]]<=x){
                f[j]=max(f[j],f[i]+1);
                if(f[j]>=k)return true;
            }
    return false;
}
int QAQ(){
    freopen("osu.in","r",stdin);
    freopen("osu.out","w",stdout);
    n=read(),k=read();
    for(int i=1;i<=n;++i)
        a[i].t=read(),a[i].x=read(),a[i].y=read();
    for(int i=0;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            id[i][j]=++cnt;
    for(int i=0;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            b[id[i][j]].v=getdis(i,j),b[id[i][j]].id=id[i][j];
    sort(b+1,b+cnt+1);
    for(int i=1;i<=cnt;++i)
        sx[b[i].id]=i;
    register int l=1,r=cnt,mid;
    while(l<r){
        mid=(l+r)>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    b[l].v.print();
    return false;
}
}
int main(){
    return orz::QAQ();
}

map


这回考试的难度是单调递减的,最后一题比较水。首先一眼就可以看出来这是个Tarjan题,在一个边双里的点一定是安全点对,边双缩点后原图变成一棵树,点权为对应原图的点数。在加了一条边后对树上的一条链都有影响,我们设这条链上点的总数为sum,一个点上的点数为\(a_i\),对于每一个点,它对答案的贡献都是\(a_i(sum-a_i)=a_isum-a_i^2\)我们把它们求一个和,变成了\(sum^2-\sum a_i^2\)。我们维护两个数组,一个是根节点到这个点的点数和,一个是根节点到这个点的点数的平方和。求个LCA一减就出来了。复杂度\(O(qlogn)\)

代码

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
namespace orz{
const int N=1001000;
struct graph{
    int head[N],ver[N<<1],next[N<<1],tot;
    void add(int x,int y){
        next[++tot]=head[x],head[x]=tot,ver[tot]=y;
        next[++tot]=head[y],head[y]=tot,ver[tot]=x;
    }
}a,b;
int n;
int dfn[N],low[N];
int c[N],cut[N<<1];
long long sum[N],mi[N];
long long S[N],M[N];
bool vis[N];
int tol=0;
int cnt=0;
int top[N],son[N],fa[N],depth[N],size[N];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
void tarjan(int x,int edge){
    low[x]=dfn[x]=++tol;
    vis[x]=true;
    int y;
    for(int i=a.head[x];i;i=a.next[i]){
        if(i==(edge^1))continue;
        if(vis[y=a.ver[i]])low[x]=min(low[x],dfn[y]);
        else {
            tarjan(y,i),low[x]=min(low[x],low[y]);
            if(low[x]<low[y])cut[i]=cut[i^1]=true;
        }
    }
}
void BFS(){
    int x,y;
    for(int i=1;i<=n;++i)vis[i]=0;
    queue<int>q;
    for(int i=1;i<=n;++i){
        if(vis[i])continue;
        ++cnt;
        q.push(i);
        while(q.size()){
            x=q.front(),q.pop();
            if(vis[x])continue;
            vis[x]=true;
            c[x]=cnt;++sum[cnt];
            for(int i=a.head[x];i;i=a.next[i]){
                if(cut[i])continue;
                if(vis[y=a.ver[i]])continue;
                q.push(y);
            }
        }
    }
}
void dky(int x,int father){
    size[x]=1;fa[x]=father;
    int mx=0;
    int y;
    for(int i=b.head[x];i;i=b.next[i]){
        if((y=b.ver[i])==father)continue;
        depth[y]=depth[x]+1;
        S[y]=S[x]+sum[y];
        M[y]=M[x]+mi[y];
        dky(y,x);
        size[x]+=size[y];
        if(size[y]>mx){
            mx=size[y];
            son[x]=y;
        }
    }
}
void dfs(int x,int topf){
    top[x]=topf;
    int y;
    if(son[x])dfs(son[x],topf);
    for(int i=b.head[x];i;i=b.next[i]){
        if((y=b.ver[i])==fa[x])continue;
        if(y==son[x])continue;
        dfs(y,y);
    }
}
inline int LCA(int x,int y){
    while(top[x]^top[y]){
        if(depth[top[x]]>depth[top[y]])x=fa[top[x]];
        else y=fa[top[y]];
    }
    return depth[x]<depth[y]?x:y;
}
inline long long solve(int x,int y){
    int t=LCA(c[x],c[y]);
    return (S[c[x]]+S[c[y]]-2*S[t]+sum[t])*(S[c[x]]+S[c[y]]-2*S[t]+sum[t])
        -(M[c[x]]+M[c[y]]-2*M[t]+mi[t]);
}
int QAQ(){
    freopen("map.in","r",stdin);
    freopen("map.out","w",stdout);
    int m,q;
    a.tot=b.tot=1;
    long long ans=0;
    n=read(),m=read(),q=read();
    for(int i=1;i<=m;++i)
        a.add(read(),read());
    tarjan(1,0);
    BFS();
    for(int i=2;i<=a.tot;i+=2){
        if(c[a.ver[i]]==c[a.ver[i^1]])continue;
        b.add(c[a.ver[i]],c[a.ver[i^1]]);
    }
    for(int i=1;i<=n;++i)
        mi[i]=sum[i]*sum[i];
    depth[1]=1;
    S[1]=sum[1];
    M[1]=mi[1];
    dky(1,0);
    dfs(1,1);
    while(q--)
        ans+=solve(read(),read());
    printf("%lld",ans);
    return false;
}
}
int main(){
    return orz::QAQ();
}

[P2000]拯救世界

写DP写累了来颓一会式子。
我们为每一个物品建立一个生成函数,用于召唤两个大神的同名神石视作不同的物品。
\(G_1(x)=x^0+x^6+x^{12}+...=\frac{1}{1-x^6}\)
\(G_2(x)=x^0+x^1+...+x^9=\frac{1-x^{10}}{1-x}\)
\(G_3(x)=x^0+x^1+...+x^5=\frac{1-x^6}{1-x}\)
\(G_4(x)=x^0+x^4+x^8+...=\frac{1}{1-x^4}\)
\(G_5(x)=x^0+x^1+...+x^7=\frac{1-x^8}{1-x}\)
\(G_6(x)=x^0+x^2+x^4+...=\frac{1}{1-x^2}\)
\(G_7(x)=x^0+x^1=x+1=\frac{1-x^2}{1-x}\)
\(G_8(x)=x^0+x^8+x^{16}+...=\frac{1}{1-x^8}\)
\(G_9(x)=x^0+x^{10}+x^{20}+...=\frac{1}{1-x^{10}}\)
\(G_{10}(x)=x^0+x^1+x^2+x^3=\frac{1-x^4}{1-x}\)

然后我们把这一大坨东西乘起来。
\(G(x)=(\frac{1}{1-x^6})(\frac{1-x^{10}}{1-x})(\frac{1-x^6}{1-x})(\frac{1}{1-x^4})(\frac{1-x^8}{1-x})(\frac{1}{1-x^2})(\frac{1-x^2}{1-x})(\frac{1}{1-x^8})(\frac{1}{1-x^{10}})(\frac{1-x^4}{1-x})\)
能约的约一下
\(G(x)=(\frac{1}{1})(\frac{1}{1-x})(\frac{1}{1-x})(\frac{1}{1})(\frac{1}{1-x})(\frac{1}{1})(\frac{1}{1-x})(\frac{1}{1})(\frac{1}{1})(\frac{1}{1-x})\)
\(G(x)=\frac{1}{(1-x)^5}\)

我们有如下公式:
\(\frac{1}{(1-x)^n}=\sum_{i=0}^\infin C_{n+i-1}^{i}x^i\)
带入得:
\(G(x)=\sum_{i=0}^\infin C_{i+4}^ix^i\)
所以所求答案为:\(C_{i+4}^i=C_{i+4}^4=\frac{P_{i+4}^4}{4!}=(n+1)(n+2)(n+3)(n+4)/24\)
因为数据非常大所以应该用时间复杂度为\(O(n\log n)\)的高精乘。
但是我不会。
用py就AC了

[P2762]太空飞行问题

我们发现实验对答案的贡献是正的,而仪器对答案的贡献的是负的,所以我们可以先假设所有的实验都选了,选择一个实验意味着不做这个实验,而选择一个仪器意味着使用了这个仪器。
所以我们要求最小的负贡献。
所以源点向所有仪器连容量为仪器费用的边,仪器向对应的汇点连容量为无穷大的边,实验向汇点连容量为实验价值的边。

我们求最小割。
首先最小割中不会有无穷大的边。
割掉实验后仪器就不一定要割
割掉所有仪器后实验就可以不选
这样符合题意,所以可以求最小割

这一题的输入十分诡异,好心的出题人给我们了一种输入方法。
关于最后的方案输出,最后还有depth的点就是选择的实验。
这个好像是与最大权闭合子图有关,之后再写个具体的博客。

#include<bits/stdc++.h>
using namespace std;
namespace orz{
#define IT vector<int>::iterator
const int N=100000,inf=1<<29;
int head[N],ver[N<<1],next[N<<1],edge[N<<1],tot=1;
int depth[N],maxflow;
int cost[N];
int value[N];
int n,m;
int s,t;
bool shiyan[N];
bool flag;
bool yiqi[N];
vector<int>equipment[N];
inline bool BFS(){
    int x,y;
    for(int i=1;i<=t;++i)
        depth[i]=0;
    queue<int>q;
    q.push(s);
    depth[s]=1;
    while(q.size()){
        x=q.front(),q.pop();
        for(int i=head[x];i;i=next[i]){
            if(!depth[y=ver[i]]&&edge[i]){
                depth[y]=depth[x]+1;
                if(y==t)return true;
                q.push(y);
            }
        }
    }
    return false;
}
inline int dinic(int x,int flow){
    if(x==t)return flow;
    int k,y,rest=flow;
    for(int i=head[x];i&&rest;i=next[i]){
        if(edge[i]&&depth[y=ver[i]]==depth[x]+1){
            k=dinic(y,min(edge[i],rest));
            depth[y]=k?depth[y]:0;
            edge[i]-=k;
            edge[i^1]+=k;
            rest-=k;
        }
    }
    return flow-rest;
}
inline void add(int x,int y,int z){
    next[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z;
    next[++tot]=head[y],head[y]=tot,ver[tot]=x,edge[tot]=0;
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    if(t=='\n')flag=false;
    return a*b;
}
char tools[10000];
int QAQ(){
//  freopen("qwq.in","r",stdin);
    n=read(),m=read();
    int sum=0;
    s=n+m+1;
    t=s+1;
    for(int i=1;i<=n;++i){
        sum+=(value[i]=read());
        memset(tools,0,sizeof tools);
        cin.getline(tools,10000);
        int ulen=0,tool;
        while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用
        {   //tool是该实验所需仪器的其中一个      
            //这一行,你可以将读进来的编号进行储存、处理,如连边。
            equipment[i].push_back(tool);
            if (tool==0) 
                ulen++;
            else {
                while (tool) {
                    tool/=10;
                    ulen++;
                }
            }
            ulen++;
        }
    }
    for(int i=1;i<=m;++i)
        cost[i]=read();
    for(int i=1;i<=n;++i){
        add(s,i,value[i]);
        for(IT j=equipment[i].begin();j!=equipment[i].end();++j)
            add(i,*j+n,inf);
    }
    for(int i=1;i<=m;++i)
        add(i+n,t,cost[i]);
    while(BFS())
        maxflow+=dinic(s,inf);
    for(int i=1;i<=n;++i){
        if(depth[i])
            shiyan[i]=true;
    }
    for(int i=1;i<=n;++i)
        if(shiyan[i]){
            printf("%d ",i);
            for(IT j=equipment[i].begin();j!=equipment[i].end();++j)
                yiqi[*j]=true;
        }
    putchar('\n');
    for(int i=1;i<=m;++i)
        if(yiqi[i])
            printf("%d ",i);
    putchar('\n');
    printf("%d",sum-maxflow);
    return false;
}
}
int main(){
    return orz::QAQ();
}

懵逼钨丝繁衍学习笔记

我懵逼了QAQ

前言

因为这篇和数论关系比较大,文中所有的\(\perp\)表示互质,(a,b)表示gcd

数论函数

如果一个函数的定义域为正整数,值域为复数,那么将它称为数论函数。

积性函数

如果数论函数\(f(n)\)满足:对于互质\(p,q\)\(f(p\cdot q)=f(p)\cdot f(q)\)
这样的函数称为(数论)积性函数。
如果没有互质的限制则称为完全积性函数。

对于所有积性函数\(f(1)=1\)
积性函数的积也是积性函数

常见的积性函数:
除数函数\(\sigma_k(n)\):表示n的约数的k次幂和
约数和函数\(\sigma_1(n)\),或\(\sigma(n)\)
约数个数函数\(r(n)\),一般也写为\(d(n)\)
欧拉函数\(\varphi(n)\)
莫比乌斯函数\(\mu(n)\)
以下几个为完全积性函数:
元函数\(e\)当命题为真时返回1
狄利克雷卷积单位函数\(\varepsilon(n)=(n==1?1:0)\)
常函数\(1(n)=1\)
单位函数\(id(n)=n\)

欧拉函数

之前讲过但我没怎么听懂
欧拉函数的公式:
\(\varphi(n)=n\prod(1-\frac{1}{p_i})\)
\(\varphi(p^n)=p^n-p^{n-1}\)
下面那个式子相当于所有数减去不互质的个数,用下面的式子回带算数唯一分解的式子可以得到上面的式子。
欧拉函数的重要式子:
\(\sum\limits_{d|n}\varphi(d)=n\)
感性理解:
我们写出如下的几个分数,以12为例:
\(\frac{1}{12},\frac{2}{12},\frac{3}{12},\frac{4}{12},\frac{5}{12},\frac{6}{12},\frac{7}{12},\frac{8}{12},\frac{9}{12},\frac{10}{12},\frac{11}{12},\frac{12}{12}\)
我们把它们化简得到:
\(\frac{1}{12},\frac{1}{6},\frac{1}{4},\frac{1}{3},\frac{5}{12},\frac{1}{2},\frac{7}{12},\frac{2}{3},\frac{3}{4},\frac{5}{6},\frac{11}{12},\frac{1}{1}\)
我们会发现在分母上所有的12的因子都被枚举到,即所有的\(d|n\)
而对于每个\(d|n\)所有与它互质的分子也被枚举到了,也就是说有\(\varphi(d)\)个。
它们的数目明显是n
比较理性的证明我不会
这个式子可以暴力硬解一些问题,因为右面是n,所以所有的正整数都可以带这个式子。

\(\sum\limits_{d\perp n}d=\frac{n\times\varphi(n)}{2}\)
由欧几里得定理得:\((d,n)=d(n-d,n)\)所以互质的数是成对出现的,做一个类似倒序相加的处理就可以的到这个结论。

莫比乌斯函数

\[ \mu(x)=\left\{ \begin{aligned} 1 && (n=1)\\ 0 && (n有平方因子) \\ (-1)^k && (n=\prod\limits_{i=1}^kp^i) \end{aligned} \right. \]
极其重要的结论:
\(\sum\limits_{d|n}\mu(d)=\varepsilon(n)\)
证明:
对于n=1的情况显然成立。
一个n的约数是由从n的唯一算数分解定理分解出的数中选几个数的到的,因为一个质因子只要选择了2个及以上的指数,它对答案的贡献就为0。
所以我们把n的分解出的质因数的指数都忽略,从中选择一些质数来组成我们的\(d\)
我们选择出有k个质因子的d的方案数为\(C_n^k\),因为加上偶数个的减去奇数个的,所以最后为0。
这个式子也可以拿来ning干(解题)。

狄利克雷卷积

狄利克雷卷积是一种函数间的运算。
\(h(n)=\sum\limits_{d|n}f(d)g(\frac{n}{d})\)
h即为f与g运算后得到的新函数。
看起来就像是一般卷积的更数论的形式。
性质
狄利克雷卷积有一些很好的性质

  1. 积性函数的狄利克雷卷积仍然满足积性
  2. 完全积性函数的狄利克雷卷积不一定满足完全积性
  3. Dirichlet卷积同时也具有交换律、分配律
  4. Dirichlet卷积运算存在单位元:\(f\cdot\varepsilon=\varepsilon\cdot f=f\)

性质4的简单证明:
因为只有\(d=n\)的时候\(\varepsilon\)才为1,所以它成立。

筛法

筛法可以筛各种积性函数以及一些奇怪的东西。

埃拉托斯特尼筛

简称埃氏筛
复杂度为\(O(n\log\log n)\)复杂度十分接近线性,所以你甚至可以拿它卡过一些要用线性筛的题。

筛素数

for(int i=2;i<=n;++i)
    if(!vis[i])
        for(int j=i*i;j<=n;j+=i)
            vis[j]=true;

筛欧拉函数

void euler(){
    for(int i=1;i<=n;++i) phi[i]=i;
    for(int i=2;i<=n;++i)
        if(phi[i]==i)
            for(int j=i;j<=n;j+=i) //必须从i开始
                phi[j]=phi[j]/i*(i-1);
}

这个相当于直接用公式算的,并没有用到积性。

线性筛

记录了每一个数的最小质因子,从而实现每一个数只有它的最小质因子筛到,实现了\(O(n)\)
线性筛素数

inline void Prime(){
    register int k;
    for(int i=2;i<=n;++i){
        if(!mindiv[i])
            prime[++tot]=i,mindiv[i]=i,is_prime[i]=true;
        for(int j=1;j<=tot&&((k=i*prime[j]<=n)&&prime[j]<=mindiv[i];++j)
            mindiv[k]=prime[j];
    }
}

另一种写法

for(int i=2;i<=n;++i){
    if(!vis[i]) p[++cnt]=i;
        for(int j=1;j<=cnt&&i*p[j]<=n;++j){
            vis[i*p[j]]=1;
        if(i%p[j]==0) break;
    }
}

这种写法没有记录mindiv数组

线性筛欧拉函数

inline void Phi(){
    phi[1]=1;
    for(int i=2;i<=n;++i){
        if(!vis[i])prime[++tot]=i,phi[i]=i-1;
        for(int j=1;j<=tot&&i*prime[j]<=n;++j){
            vis[i*prime[j]]=true;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

关于不互质的解释:
因为\(prime[j]是i\times prime[j]\)的最小质因子,所以\(\varphi[i]\)的式子中已经有了\((1-\frac{1}{prime[i]})\)这一项,所以把\(prime[j]\)乘到前面的系数中就可以了。

线性筛莫比乌斯函数

inline void Mu(){
    mu[1]=1;
    for(int i=2;i<=n;++i){
        if(!vis[i])prime[++tot]=i,mu[i]=1;
        for(int j=1;j<=tot&&i*prime[j]<=n;++j){
            vis[i*prime[j]]=true;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
}

线性筛积性函数的总结
考虑质数怎么办,筛到最小质因子怎么办,互质怎么办。
不过要先知道它是一个积性函数,这一点经常可以从狄利克雷卷积得出。

前置知识:和式的化简方法

早上听得一脸懵逼,现在觉得说不定有必要去把《具体数学》看一下qaq
这里说一下大概的思想,一个和式可以看成一个循环的形式,我们去枚举不同的东西进行一些操作。和式的化简一般是改变枚举顺序来让式子变得更可求。
我们要注意到的是式子一定要是等价的。
比如你将一个后枚举的东西提到了前面,你就要思考它在前面的什么状态下被枚举到了,进而得到改变后的式子。

莫比乌斯反演

回顾:
\(\sum\limits_{d|n}\mu(d)=\varepsilon(n)\)
\(f\cdot\varepsilon=f\) (狄利克雷卷积)

莫比乌斯反演的式子:
\(g(m)=\sum\limits_{d|n}f(d)\Leftarrow\Rightarrow f(n)=\sum\limits_{d|n}g(d)\mu(\frac{n}{d})\)
弱推:
\(g=f\cdot 1\)
\(g\cdot\mu=f\cdot\mu\cdot 1\)
\(\mu\cdot 1=\varepsilon\)
\(g\cdot\mu=f\cdot\varepsilon\)
\(g\cdot\mu=f\)
\(f=g\cdot\mu\)

强推:

一:
已知\(g(n)=\sum\limits_{d|n}f(d)\)
推出\(f(n)=\sum\limits_{d|n}g(d)\mu(\frac{n}{d})\)
\(\sum\limits_{d|n}g(d)\mu(\frac{n}{d})\)
\(=\sum\limits_{d|n}g(\frac{n}{d})\mu(d)\)
\(=\sum\limits_{d|n}\mu(d)\sum\limits_{d'|\frac{n}{d}}f(d')\) 带入式子
我们想要把f提到前面来,因为d是n的因子,\(\frac{n}{d}\)是n的因子,d'是\(\frac{n}{d}\)的因子,所以d'是n的因子,我们在最前面枚举d',之后我们考虑对于每一个\(f(d')\)它会和那些\(\mu\)相乘。

一些例题

之后再写吧,咕咕咕

猜你喜欢

转载自www.cnblogs.com/oiertkj/p/12203495.html