本场总结:
题目类型:
A.签到
B.广义斐波那契数列求第n项----十进制倍增
C.BSGS基础题 ---预处理打表
E.位元状压dp
F.二分图求解最大独立集
G.基础dp
H.拓扑排序
D.I.J 留坑暂时不填
小结:
----广义斐波那契 可以先找最小循环节加速,然后用十进制倍增取模
----学习了一下BSGS,对于多组询问可以先预处理a^i*m来降低复杂度. 学了 手写hash
----再次复习状压dp 哎,又是智商差距
----学习了一下二分图,这道题转换是在巧妙,最大团转化为二分图求最大独立集.---还有独立集元素求取方法
----基础dp就是补想法吧
----转化拓扑思想学一学.
A. digits 2
题目大意:t组,每组输入一个n,求构造一个数字x,x是n的倍数并且x的位数之和也是n的倍数.(n<10^400)
分析:显然我们输出n个n即可。
#include<bits/stdc++.h> using namespace std; int main() { int t; cin>>t; while( t-- ) { int n; cin>>n; string s; for( int i=1;i<=n;i++ )cout<<n; cout<<endl; } }
B. generator 1
题目大意:直接上图(题意显然)
分析:求广义斐波那契数列第n项,我们可以先处理出模数的循环节,然后对底数进行十进制位逐位取模,然后套用矩阵快速幂板子即可.
#include<bits/stdc++.h> using namespace std; struct node { long long a[2][2]; }; const int maxn = 1e6 + 5; int ans, pri[maxn]; void init() { long long limit; for ( int i = 2; i <= 100000; i++ ) { if (!pri[i])pri[ans++] = i; for ( int j = 0; j < ans; j++ ) { limit = 1ll * i * pri[j]; if (limit > 100000)break; pri[limit] = 1; if (i % pri[j] == 0)break; } } } long long getmod( long long n ) { long long ans1 = 1, ans2 = 1,nn = n; for (int i = 0; i < ans; i++) { if (pri[i] * pri[i] > n)break; if (n%pri[i] == 0) { ans1 *= (pri[i] - 1) * (pri[i] + 1); ans2 *= pri[i]; while (n%pri[i] == 0)n /= pri[i]; } } if ( n > 1 ) { ans1 *= (n - 1) *(n + 1); ans2 *= n; } return nn / ans2 * ans1; } node mul( node a, node b, long long mod ) { node ans; memset(ans.a, 0, sizeof(ans.a)); for ( int i = 0; i < 2; i++ ) { for ( int j = 0; j < 2; j++ ) { for ( int k = 0; k < 2; k++ ) { ans.a[i][j] = (ans.a[i][j] + a.a[i][k] * b.a[k][j]) % mod; } } } return ans; } node ksm( node a, long long b, long long mod ) { node ans; // 单位矩阵 ans.a[0][0] = 1;ans.a[0][1] = 0; ans.a[1][0] = 0;ans.a[1][1] = 1; while ( b ) { if (b & 1) ans = mul(ans, a, mod); a = mul(a, a, mod); b >>= 1; } return ans; } char s[maxn]; long long ksc( long long x, long long y, long long mod ) // 防止溢出 { long long tmp = (x*y - (long long)((long double)x / mod * y + 1.0e-8)*mod); return tmp < 0 ? tmp + mod : tmp; } int main() { ios::sync_with_stdio(false); long long x, y, a, b, mod, m, n; node ans, aa; cin >> x >> y >> a >> b >> s>>mod; init(); m = getmod(mod); // 找最小循环节 n = 0; int t = strlen(s); for ( int i = 0; i < t; i++ ) { n = ksc(n, 10, m); n = n + s[i] - '0'; if (n >= m) n -= m; } aa.a[0][0] = a;aa.a[0][1] = 1; aa.a[1][0] = b;aa.a[1][1] = 0; ans = ksm(aa, n, mod); printf("%lld\n", (ans.a[1][1] * x + ans.a[0][1] * y) % mod ); return 0; }
C. generator 2
题目大意:
分析:对该式子进行化简,会发现就是一个BFGS的基础题.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e6+10; const int HashMod=1999997; inline ll read() { ll f=1,x=0;char s=getchar(); for( ;!isdigit(s);s=='-' && (f=-1),s=getchar()); for( ;isdigit(s);x=x*10+s-48,s=getchar()); return x*f; } ll qpow( ll a,ll b,ll mod ) { ll ans=1; while( b ) { if( b&1 ) ans=ans*a%mod; b>>=1; a=a*a%mod; } return ans; } namespace BSGS { ll tt,m,lim; struct HashTable // 手写hash { struct node{ ll u,v,next; }e[1000005]; ll head[HashMod],cnt; void add( ll u,ll v,ll w ) { e[++cnt]=(node){ w,v,head[u] }; head[u]=cnt; } void Clear() { memset(head,0,sizeof(head)); cnt=0; } void Hash( ll x,ll k ) { ll s=x%HashMod; add(s,k,x); } ll query( ll x ) { ll s=x%HashMod; for( ll i=head[s];i;i=e[i].next ) { if( e[i].u==x ) return e[i].v; } return -1; } }Hash; void init( ll y,ll p ) { m=(ll)pow(p,2.0/3);Hash.Clear(); // 适当更改块的大小和Hash的块的大小 tt=qpow( qpow(y,m,p),p-2,p); // 1/a^m lim=p/m+1; Hash.Hash(1,0); ll t=1; for( int i=1;i<m;i++ ) { t=t*y%p; if( Hash.query(t)==-1 ) Hash.Hash(t,i); } } ll cal( ll b,ll p ) { if( b==1 ) return 0; for( int i=0;i<lim;i++ ) { int k=Hash.query(b); if( k!=-1 ) return 1ll*i*m+k; b=tt*b%p; } return -1; } } ll n; // a ^ n = ( a - 1 ) * v + b / ( ( a - 1 ) * x0 + b ) void print( ll x ) { printf("%lld\n", x<n ? x : -1 ); } void solve() { ll x0,a,b,p,q,v; n=read();x0=read();a=read();b=read();p=read(); q=read(); if( a==0 ) //如果a=0,看v与x0和b的值 { while( q-- ) { v=read(); if( v==x0 ) print(0); else if( v==b ) print(1); else print(-1); } return; } else if( a==1 ) //如果a=1,分b=0和b!=0两种情况讨论 { if( b==0 ) { while( q-- ) { v=read(); if( v==x0 ) print(0); else print(-1); } } else { while( q-- ) // x0 + n*b = v ---> n=( v - x0 )/ b { v=read(); v=(v-x0+p)%p*qpow(b,p-2,p)%p; print(v); } } return; } ll inv=( (a-1)*x0+b )%p; if( inv==0 ) //左边为 0,如果右边也为 0则是 0,否则是-1 { while( q-- ) { while( q-- ) { v=read(); if( ( (a-1)*v+b )%p==0 ) print(0); else print(-1); } } return; } inv=qpow(inv,p-2,p); BSGS::init(a,p); // 预处理a^(m*t1) while( q-- ) { v=read(); if( v==x0 ) print(0); else { ll res=( (a-1)%p*v%p+b )%p*inv%p; // a^n=((a-1)*v+b)/((a-1)*x0+b) 设res=((a-1)*v+b)/((a-1)*x0+b) ll ans=BSGS::cal(res,p); print(ans); } } } int main() { int t; t=read(); while( t-- ) solve(); return 0; }
E. independent set 1
题目大意:n个点,m条边,求所有点集的最大独立点集之和. ( n<26 , m<n*(n-1)/2 )
最大独立点集的定义:给定点集中,选取最多的点(满足两个互异的点不相连)构成的点集,集合元素个数即为值.
分析:显然我们必须枚举所有的点集,去求对应的最大独立点集. 用位元状压dp.注意:这题有个坑点,内存限制只能用char类型.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1<<26; int val[27]; char dp[maxn]; char max( char a,char b ){ return a > b ? a : b; } int main() { int n,m; cin>>n>>m; for( int i=1;i<=m;i++ ) { int a,b;cin>>a>>b; val[a]|=(1<<b); val[b]|=(1<<a); } for( int i=0;i<n;i++ ) { val[i]|=(1<<i); val[i]=~val[i]; } ll ans=0; for( int i=1;i<(1<<n);i++ ) { int pos; for( int j=0;j<26;j++ ) { if( i>>j & 1 ) { pos=j; break; } } dp[i]=max( dp[i^(1<<pos)],dp[ ( i & val[pos] ) ]+1 ); ans+=dp[i]; } cout<<ans<<endl; }
F.maximum clique 1
题目大意:给一个集合(大小5e3),由于是集合,所以每个元素都不同,求一个最大子集合使得子集合内每两个点最少有2个二进制bit位不同
分析:对于集合内每对bit位最少2个不同的,在两点之间连一条边,于是题目就变成了在图论中求一个最大团,但是最大团不是NPC问题么,只能指数级别算呀,这可不行。于是可以看这个边有什么性质,图里的边要满足bit差异至少有2个,那么由于元素不同,它反过来就是bit位差异为1,那么补图的某一边存在当且仅当bit差异为1,相当于就是求补图的最大独立集,再想想,bit差异为1????,那岂不是可以根据二进制中1的个数划分为二分图,求二分图的最大独立集.
如何求二分图的最大独立集: 点数 - 二分图最大匹配数 ( 证明自己琢磨 )
我们规定 二进制中1个数为偶数的 数集合为二分图的一边,我们从这个集合入手( 另一边情况算法相同).
- 先跑一遍二分图匹配,会有不相关的点和失配点 不会被匹配.
- 再用失配点和不相关的点跑一遍二分图匹配,就可以得二进制中1个数为偶数的 数集合的最大独立集,然后再加上非匹配 二进制中1个数为奇数结点,即为答案
#include<bits/stdc++.h> using namespace std; const int maxn=5002; int p[maxn]; bool vis[maxn],jud[maxn],used[maxn]; int master[maxn]; vector<int>g[maxn]; bool check( int x,int y ) { return __builtin_popcount(p[x]^p[y])==1 ; } bool find( int x ) { vis[x]=1; for( auto v:g[x] ) { if( used[v] ) continue; used[v]=1; if( !master[v] || find(master[v]) ) { master[v]=x; return 1; } } return 0; } int main() { int n; scanf("%d",&n); for( int i=1;i<=n;i++ ) scanf("%d",&p[i]); for( int i=1;i<=n;i++ ) { for( int j=i+1;j<=n;j++ ) { if( check(i,j) ) { g[i].push_back(j); g[j].push_back(i); } } } for( int i=1;i<=n;i++ ) { memset(used,0,sizeof(used)); if( __builtin_parity(p[i])==1 ) //规定 二进制下1的个数为偶数的数 进行 二分图匹配 { if( find(i) ) jud[i]=1; // 该点可以匹配 } } memset(used,0,sizeof(used)); memset(vis,0,sizeof(vis)); for( int i=1;i<=n;i++ ) { if( __builtin_parity(p[i])==1 && !jud[i] ) // 失配元素 匹配 { find(i); } } vector<int> ans; for( int i=1;i<=n;i++ ) { if( __builtin_parity(p[i])==1 && vis[i] ) ans.push_back(p[i]); if( __builtin_parity(p[i])==0 && !used[i] ) ans.push_back(p[i]); } cout<<ans.size()<<endl; for( auto v:ans ) cout<<v<<" "; return 0; }
G. subsequence 1
题目大意:给定t组样例,每组样例输入n,m,字符串s,t(都由数字构成).求s中的子序列构成的数字大于t的方案数.
分析:此题两个解法.
解法一: (题解方法)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=3e3+10; const int mod=998244353; char s[maxn],t[maxn]; ll C[maxn][maxn],dp[maxn][maxn]; void init() { for( int i=0;i<maxn;i++ ) C[i][0]=C[i][i]=1; for( int i=2;i<maxn;i++ ) { for( int j=1;j<i;j++ ) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } } int main() { init(); int T,n,m; cin>>T; while( T-- ) { cin>>n>>m; cin>>(s+1); cin>>(t+1); ll ans=0; for( int i=1;i<=n-m;i++ ) { if( s[i]!='0' ) { for( int j=m;j+i<=n;j++ ) ans=(ans+C[n-i][j])%mod; } } for( int i=1;i<=m;i++ ) // t 的 i个后缀 { for( int j=i;j<=n;j++ ) // s 的 j 个后缀 { if( t[m-i+1]==s[n-j+1] ) dp[i][j]=( dp[i-1][j-1]+dp[i][j-1] )%mod; if( t[m-i+1]<s[n-j+1] ) dp[i][j]=( C[j-1][i-1]+dp[i][j-1] )%mod; if( t[m-i+1]>s[n-j+1] ) dp[i][j]=dp[i][j-1]; } } // for( int i=1;i<=m;i++ ) // { // for( int j=1;j<=n;j++ ) printf("(%d)(%d) -%d- ",i,j,dp[i][j]);cout<<endl; // } ans=(ans+dp[m][n])%mod; cout<<ans<<endl; } }
解法二:我们要找的序列的长度分两种,一种大于t的长度,一种等于t的长度.大于t的长度的序列贡献可以用组合数求得.
等于t的长度的序列贡献,分为两种,一个是前i个字符与t对应相同,一个是第一个字符就大于t[0]两种贡献,用个g[]维护即可.
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=3e3+10; const int mod=998244353; char s[maxn],t[maxn]; ll g[maxn],f[maxn][maxn]; // g 维护 前i位相同的 方案数 void init() // 组合数打表 { for( int i=0;i<maxn;i++ ) f[i][0]=f[i][i]=1; for( int i=2;i<maxn;i++ ) { for( int j=1;j<i;j++ ) f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod; } } int main() { init(); int T,n,m; cin>>T; while( T-- ) { cin>>n>>m; cin>>(s+1); cin>>(t+1); g[0]=1; for( int i=1;i<=m;i++ ) g[i]=0; ll ans=0; for( int i=1;i<=n;i++ ) { if( s[i]!='0' ) //求 以 s[i]为首字符 len>len(t) 贡献 { for( int j=m;j<=n-i;j++ ) ans=(ans+f[n-i][j])%mod; } for( int j=1;j<=min(i,m);j++ ) // 前j-1位相等 第j位为 i len=len(t) 的贡献 { if( s[i]>t[j] ) ans=(ans+f[n-i][m-j]*g[j-1])%mod; } for( int j=min(i,m);j;j-- ) // 更新g数组 { if( s[i]==t[j] ) g[j]=(g[j]+g[j-1])%mod; } } cout<<ans<<endl; } }
H. subsequence 2
**题目大意:https://ac.nowcoder.com/acm/contest/885/H 自行读题叭
分析:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+5; vector<int> g[maxn]; int d[maxn]; char str[maxn]; char num[maxn]; int ch[12][maxn]; int idd[maxn]; //编号 对应 字符 int cnt; int get_id( int id,int num ) // 获取当前 第num个字符 的 编号 { if( ch[id][num]==0 ) { ch[id][num]=++cnt; idd[cnt]=id; } return ch[id][num]; } int main() { int n,m; cin>>n>>m; for( int i=1;i<=m*(m-1)/2;i++ ) { char s[3]; int len; cin>>s>>len; if( len ) { cin>>str; int num1=0,num2=0; int pre=-1; for( int j=0;j<len;j++ ) { int id; if( str[j]==s[0] ) { id=get_id(str[j]-'a',num1); num1++; } else { id=get_id(str[j]-'a',num2); num2++; } if( pre!=-1 ) // 相邻字符建边 { d[id]++; g[pre].push_back(id); } pre=id; } } } if( cnt!=n ) { puts("-1"); return 0; } queue<int> que; vector<int> ans; for( int i=1;i<=n;i++ ) if( d[i]==0 ) que.push(i); // 拓扑排序 while( !que.empty() ) { int u=que.front();que.pop(); ans.push_back(u); for( auto v:g[u] ) { d[v]--; if( d[v]==0 ) que.push(v); } } if( ans.size()!=n ) { puts("-1"); return 0; } for( auto v:ans ) putchar( 'a'+idd[v] ); }