Day 2
rank 11 100+35+30=165
本题是一道数论题,求ax+by=c的正整数对(x,y) x>=0并且y>=0
先说下gcd: 求a,b公约数gcd(a,b)
如gcd(4,6)= 2
辗转相除法 gcd(a,b)=gcd(b,a%b)
证明一下,令a=kb+r,那么r=a%b;
设d为(a,b)的一个任意公约数d,所以d|a且d|b
因为r=a-kb因为d|a且d|b,所以d|r注意到我们的d是任意选取的,
那么最大公约数是属于这个公因数集合里的所以gcd(a,b)=gcd(b,a%b)
再说下ex_gcd(a,b,&x,&y)求ax+by=gcd(a,b)的一个整数解 x,y
算法如下:
b=0时gcd(a,b)=gcd(a,0)=a;所以ax=a,所以x=1,y=任意数(这里赋值为0)
ax1+by1=gcd(a,b)=gcd(b,a%b)=bx2+(a%b)y2
引理: a-[a/b]*b=a%b
令a=br+k,左边=br+k-[(br+k)/b]*b=br+k-br=k=a%b=右边
由引理得:ax1+by1=bx2+(a-[a/b]*b)y2=ay2+b(x2-[a/b*b]y2)
由恒等式定理得: x1=y2,y1=x2-[a/b*b]y2
所以求解(x1,y1)只要求出(x2,y2)就可以了
ex_gcd程序如下:
ll ex_gcd(ll a,ll b,ll &x,ll &y) { if (b==0) { x=1;y=1; return a;} ll r=ex_gcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; return r; }
但是这只是求出一组解但我们要求多组解甚至解的个数
令一组解(x0,y0)是初始解 (x1,y1)是要求的解
ax0+by0=gcd(a,b)=ax1+by1
a(x0-x1)=b(y1-y0)两边同时除以gcd(a,b)
设gcd(a,b)=g;a'=a/g;b'b/g
那么就可以写成 a'(x0-x1)=b'(y1-y0) 因为g为a,b最大公约数
那么 a'和b'互质
那么b'|(x0-x1);a'|(y1-y0)设x0-x1=kb'那么y1-y0=ka'
所以x1=x0-kb';y1=y0+ka' k为整数
由此可见方程ax+by=gcd(a,b)如果有解那么一定有无线组解
(x0,y0)==>(x0-kb',y0+ka')
对于一般2元1次不等式ax+by=c若c%gcd(a,b)!=0那么一定无解否则一定有多组解
回到题目让我们求出ax+by=c,通过上面那句话就可以轻易判断有无解,现在考虑解的个数
先把x0 y0调整到正数
x最小时且大于0,y最大,x0-kb'>=0不停的增加k最多可以是kmax= x0/b';xmin=x0-(x0/b')*b'=x0%b'
x最大时y最小且大于0,y0+ka'>=0不停的减去k最多可以是kmin=-y0/a'同理ymin=y0%a'
通过ymin可以求出xmax(带入即可)
求出xmin和xmax后再xmin和xmax之间每隔b‘就有一组解所以就是一个等差数列,个数是(尾项-首相)/公差+1=(xmax-xmin)/b'+1
# include <bits/stdc++.h> using namespace std; typedef long long ll; ll ex_gcd(ll a,ll b,ll &x,ll &y) { if (b==0) { x=1;y=1; return a;} ll r=ex_gcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; return r; } inline ll read() { ll X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } int main() { freopen("cake.in","r",stdin); freopen("cake.out","w",stdout); int T; scanf("%d",&T); while (T--) { ll a=read(),b=read(),x,y,c=read(); ll g=ex_gcd(a,b,x,y); if (c%g!=0) { printf("0\n"); continue; } ll g1=a/g,g2=b/g;ll k=c/g; x=x*(k%g2); y=y*(k%g1); //后面反正要%g1的现在先%防止爆炸 ll xmin=(x%g2+g2)%g2,ymin=(y%g1+g1)%g1; ll xmax=(c-b*ymin)/a; //通过ymin求xmax if (xmax-xmin<0) { printf("0\n"); continue; } ll ans=((xmax-xmin)/g2)+1;//等差数列个数 printf("%lld\n",ans); } return 0; }
30pts:
# include <bits/stdc++.h> using namespace std; char s[1005]; bool fun(int z,int a,int c,int k,int m,int n) { for (int i=1;i<=n;i++) { z=((a*z+c)/k)%m; if ((z<m/2)&&(s[i]!='0')) return false; else if ((z>=m/2)&&(s[i]!='1'))return false; } return true; } int main() { freopen("zero.in","r",stdin); freopen("zero.out","w",stdout); int a,c,k,m,n; scanf("%d%d%d%d%d",&a,&c,&k,&m,&n); scanf("%s",s+1); int ans=0; for(int z=0;z<=m;z++) { if (fun(z,a,c,k,m,n)) ans++; } printf("%d\n",ans); return 0; }
100pts:倍增+hash
题解原文:
倍增HASH,用倍增记录每个值跳2^i次后会到哪个值,构成的串的HASH值,
然后根据n的二进制直接算答案就行了,倍增的时候要滚存。
#include <bits/stdc++.h> using namespace std; const int mo1 = 1000000007; const int mo2 = 1000000009; const int base1 = 233; const int base2 = 23333; const int M = 1100010; const int N = 100010; vector<int>a[M]; pair<int, int>HASH; int to[M][2], Pow1[N], Pow2[N], now[M]; pair<int, int>Hash[M][2], nowhash[M]; int ans; char s[N]; inline void Ch(pair<int, int> &now, int C) { now.first = 1ll * now.first * Pow1[C] % mo1; } inline void Add(pair<int, int> &now, pair<int, int> A) { now.first = now.first + A.first; if(now.first >= mo1)now.first -= mo1; } inline int check(pair<int, int> a, pair<int, int> b) { return ((a.first == b.first) && (a.second == b.second)); } int main() { freopen("zero.in", "r", stdin); freopen("zero.out", "w", stdout); int A, c, k, m, n; Pow1[0] = Pow2[0] = 1; scanf("%d%d%d%d%d", &A, &c, &k, &m ,&n); for(int i = 1;i <= n;i++) { Pow1[i] = 1ll * Pow1[i - 1] * base1 % mo1; } int M = m >> 1; for(int i = 0;i < m;i++) { int z = i; now[i] = i; z = ((1ll * A * z + c) / k) % m; to[i][0] = z; Hash[i][0].first = (z >= M) + 1; } scanf("%s", s); if(n & 1) { for(int i = 0;i < m;i++) { nowhash[i] = Hash[now[i]][0]; now[i] = to[now[i]][0]; } } for(int i = 0;i < n;i++) { HASH.first = (1ll * HASH.first * base1 + s[i] - '0' + 1) % mo1; } for(int x = 1;x <= 17;x++) { for(int i = 0;i < m;i++) { to[i][x & 1] = to[to[i][(x - 1) & 1]][(x - 1) & 1]; Hash[i][x & 1] = Hash[i][(x - 1) & 1]; Ch(Hash[i][x & 1], 1 << (x - 1)); Add(Hash[i][x & 1], Hash[to[i][(x - 1) & 1]][(x - 1) & 1]); } if(n & (1 << x)) { for(int i = 0;i < m;i++) { Ch(nowhash[i], 1 << x); Add(nowhash[i], Hash[now[i]][x & 1]); now[i] = to[now[i]][x & 1]; } } } for(int i = 0;i < m;i++) if(check(nowhash[i], HASH))ans++; printf("%d\n", ans); }
题意:
给一个DAG,选择尽量多的点使彼此之间不存在祖先-后代关系。
5%:
暴力枚举每一个点是否被选中,时间复杂度:O(2^n)
20%:
在上一个做法的基础上加上一些剪枝。时间复杂度:O(2^n)。
此算法亦可通过n==200的测试点,且只需要16ms(luogu上)。
树的部分分:
容易发现选择全部叶节点即可。时间复杂度:O(n)
“每个会员要么没有上司,要么没有下属”的:
容易发现此时的DAG是一个二分图。使用经典的二分图最大独立集算法(点数-最大匹配数)即可。时间复杂度:O(m*sqrt(n))。
“全是直接下属”的:
我也不知道怎么做,这个部分分只是为了让***的错误算法多拿一些分。
100%:
容易发现此题是一道DAG最大独立集裸题(参见 CTSC2008祭祀),而且不用输出方案。(但我真的不是出的原题,纯属巧合)
由于时间原因,在此复制@白苏小公子喵 的题解:
在有向无环图中,我们定义:
链:图上一些点的集合,对于链上任意两个点x、y,满足x能到达y或者y能到达x。
反链:图上一些点的的集合,对于反链上任意两个点x、y,满足x不能到达y并且y不能到达x。
所以就是很显然的求最长反链长度了~
有以下Dilworth定理:
最长反链长度=最小链覆盖(选取最少的链覆盖所有的点)->证明详见最长反链与最小链覆盖
以及其对偶定理:最长链长度=最小反链覆盖
所以就又转化成了求最小路径(链)覆盖了,来看怎么求:
选择建一个二分图,两边各有n个点,原来的点node分别对应两个图中的node1、node2。如果原图中存在边 x->y,那么就在二分图上建立边 x1->y2。
跑一遍匈牙利,则有 原图最小路径覆盖=原点数n-二分图最大匹配
(当然也可以用网络流,则有 原图最小路径覆盖=原点数n-最大流)
为什么呢?考虑每在二分图上连一条边,就相当于将两条路径连成一条,那么最小链覆盖数就减少了1(少用一条链覆盖所有点了)。我们将一个点拆成两个,跑二分图最大匹配,避免了路径相交的问题,保证所选出来的每一条一定为一条链。
#include<bits/stdc++.h> #define maxn 10010 #define int64 long long #define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout) using namespace std; bitset<maxn> isfa[maxn]; int n,m,que[maxn],cnt; int ind[maxn],top[maxn]; vector<int> v[maxn],vb[maxn]; struct edge{ int u,v,cap; }; struct Dinic{ int n,s,t,dis[maxn],cur[maxn],que[maxn]; vector<edge>e;vector<int>v[maxn]; void Init(int n){ this->n=n;e.clear(); for(int i=0;i<n;i++)v[i].clear(); } void AddEdge(int x,int y,int flw){ e.push_back((edge){x,y,flw}); e.push_back((edge){y,x,0}); v[x].push_back(e.size()-2); v[y].push_back(e.size()-1); } int bfs(){ memset(dis,0x3f,sizeof dis); int l=1,r=1;que[1]=s;dis[s]=0; while(l<=r){ int p=que[l++],to,i; for(int t=0;t<(int)v[p].size();++t)if(e[i=v[p][t]].cap && dis[to=e[i].v]>1e9) dis[to]=dis[p]+1,que[++r]=to; } return dis[t]<1e9; } int dfs(int p,int a){ if(p==t || !a)return a; int sf=0,flw; for(int &i=cur[p],to;i<(int)v[p].size();++i){ edge &E=e[v[p][i]]; if(dis[to=E.v]==dis[p]+1 && (flw=dfs(to,min(a,E.cap)))){ E.cap-=flw;e[v[p][i]^1].cap+=flw; a-=flw;sf+=flw; if(!a)break; } } return sf; } int dinic(int s,int t){ this->s=s;this->t=t; int flw=0; while(bfs()){ memset(cur,0,sizeof cur); flw+=dfs(s,1e9); } return flw; } }sol; int main(){ FO(dance); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y;scanf("%d%d",&x,&y); v[x].push_back(y);ind[y]++; vb[y].push_back(x); } int l=1,r=0; for(int i=1;i<=n;i++)if(!ind[i])que[++r]=i; while(l<=r){ int now=que[l++];top[++cnt]=now; for(int i=0;i<(int)v[now].size();i++) if(!--ind[v[now][i]])que[++r]=v[now][i]; } for(int i=1;i<=n;i++){ int p=top[i];isfa[p][p]=1; sol.AddEdge(0,i*2,1); sol.AddEdge(i*2+1,1,1); for(int j=0;j<(int)vb[p].size();j++) isfa[p]|=isfa[vb[p][j]]; for(int j=1;j<=n;j++)if(isfa[p][j] && p!=j) sol.AddEdge(j*2,p*2+1,1); } printf("%d\n",n-sol.dinic(0,1)); return 0; }