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('
');
				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
",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
",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
",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(nlog 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=='
')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('
');
	for(int i=1;i<=m;++i)
		if(yiqi[i])
			printf("%d ",i);
	putchar('
');
	printf("%d",sum-maxflow);
	return false;
}
}
int main(){
	return orz::QAQ();
}

懵逼钨丝繁衍学习笔记

我懵逼了QAQ

前言

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

数论函数

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

积性函数

如果数论函数(f(n))满足:对于互质(p,q)(f(pcdot 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)=nprod(1-frac{1}{p_i}))
(varphi(p^n)=p^n-p^{n-1})
下面那个式子相当于所有数减去不互质的个数,用下面的式子回带算数唯一分解的式子可以得到上面的式子。
欧拉函数的重要式子:
(sumlimits_{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,所以所有的正整数都可以带这个式子。

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

莫比乌斯函数

[ mu(x)=left{ egin{aligned} 1 && (n=1)\ 0 && (n有平方因子) \ (-1)^k && (n=prodlimits_{i=1}^kp^i) end{aligned} ight. ]

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

狄利克雷卷积

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

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

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

筛法

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

埃拉托斯特尼筛

简称埃氏筛
复杂度为(O(nloglog 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 imes 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
这里说一下大概的思想,一个和式可以看成一个循环的形式,我们去枚举不同的东西进行一些操作。和式的化简一般是改变枚举顺序来让式子变得更可求。
我们要注意到的是式子一定要是等价的。
比如你将一个后枚举的东西提到了前面,你就要思考它在前面的什么状态下被枚举到了,进而得到改变后的式子。

莫比乌斯反演

回顾:
(sumlimits_{d|n}mu(d)=varepsilon(n))
(fcdotvarepsilon=f) (狄利克雷卷积)

莫比乌斯反演的式子:
(g(m)=sumlimits_{d|n}f(d)LeftarrowRightarrow f(n)=sumlimits_{d|n}g(d)mu(frac{n}{d}))
弱推:
(g=fcdot 1)
(gcdotmu=fcdotmucdot 1)
(mucdot 1=varepsilon)
(gcdotmu=fcdotvarepsilon)
(gcdotmu=f)
(f=gcdotmu)

强推:

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

一些例题

之后再写吧,咕咕咕

原文地址:https://www.cnblogs.com/oiertkj/p/12203495.html