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)\)所以互质的数是成对出现的,做一个类似倒序相加的处理就可以的到这个结论。
莫比乌斯函数
极其重要的结论:
\(\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运算后得到的新函数。
看起来就像是一般卷积的更数论的形式。
性质
狄利克雷卷积有一些很好的性质
- 积性函数的狄利克雷卷积仍然满足积性
- 完全积性函数的狄利克雷卷积不一定满足完全积性
- Dirichlet卷积同时也具有交换律、分配律
- 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\)相乘。
一些例题
之后再写吧,咕咕咕