A Eddy Walker
题意
一个0到n-1的环,初始在0,每次随机前进或后退一格,当所有点都访问过就结束,问结束时站在k的概率是多少。(注意输出的是前缀积)
分析
一开始站在0,最后显然不可能在0,剩下n-1格,随机数打表发现概率相同,都为(frac{1}{n-1})
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int cnt[1050];
bool vis[1050];
void sui(int n){
memset(cnt,0,sizeof(cnt));
int num=100000;
while(num--){
memset(vis,false,sizeof(vis));
vis[0]=true;
int now=0;
while(true){
bool flag=true;
for(int i=0;i<n;i++){
if(!vis[i]){
flag=false;
}
}
if(flag){
break;
}
int rn=rand()%10;
if(rn<5){
now=(now-1+n)%n;
}else{
now=(now+1)%n;
}
vis[now]=true;
}
cnt[now]++;
}
for(int i=0;i<n;i++){
printf("%d%c",cnt[i],i==n-1?'
':' ');
}
}
ll Pow(ll a,ll n){
ll ans=1ll;
while(n){
if(n%2){
ans=ans*a%mod;
}
a=a*a%mod;
n/=2;;
}
return ans;
}
int t,n,m;
int main(){
scanf("%d",&t);
ll ans=1ll;
while(t--){
scanf("%d%d",&n,&m);
ll tmp;
if(m==0){
if(n==1){
tmp=1ll;
}else{
tmp=0;
}
}else{
tmp=Pow(1ll*(n-1),mod-2);
}
ans=(ans*tmp)%mod;
printf("%lld
",ans);
}
}
B Eddy Walker 2
题意
无限长的路,每次可以向前走1-k步,概率相同,问走到n的概率。
分析
类似于经典dp走楼梯,设dp[i]为走到i的概率,那么(dp[i]=frac{1}{k}sum_{j=i-k}^{i-1}dp[j]),这是一种线性递推的关系,所以求出前几项,然后用BM算法求出第n项。
当n为无穷大的情况,先计算走一步的期望,(sum_{i=1}^kfrac{i}{k}=frac{k+1}{2}),那么反过来,走到某一格的概率就是(frac{1}{frac{k+1}{2}}=frac{2}{k+1})
代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef long long ll;
typedef vector<ll> VI;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
// head
ll n;
namespace linear_seq {
const int N=10010;
ll res[N],base[N],_c[N],_md[N];
vector<int> Md;
void mul(ll *a,ll *b,int k) {
rep(i,0,k+k) _c[i]=0;
rep(i,0,k) if (a[i]) rep(j,0,k) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod;
for (int i=k+k-1;i>=k;i--) if (_c[i])
rep(j,0,SZ(Md)) _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod;
rep(i,0,k) a[i]=_c[i];
}
int solve(ll n,VI a,VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
ll ans=0,pnt=0;
int k=SZ(a);
assert(SZ(a)==SZ(b));
rep(i,0,k) _md[k-1-i]=-a[i];_md[k]=1;
Md.clear();
rep(i,0,k) if (_md[i]!=0) Md.push_back(i);
rep(i,0,k) res[i]=base[i]=0;
res[0]=1;
while ((1ll<<pnt)<=n) pnt++;
for (int p=pnt;p>=0;p--) {
mul(res,res,k);
if ((n>>p)&1) {
for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0;
rep(j,0,SZ(Md)) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod;
}
}
rep(i,0,k) ans=(ans+res[i]*b[i])%mod;
if (ans<0) ans+=mod;
return ans;
}
VI BM(VI s) {
VI C(1,1),B(1,1);
int L=0,m=1,b=1;
rep(n,0,SZ(s)) {
ll d=0;
rep(i,0,L+1) d=(d+(ll)C[i]*s[n-i])%mod;
if (d==0) ++m;
else if (2*L<=n) {
VI T=C;
ll c=mod-d*powmod(b,mod-2)%mod;
while (SZ(C)<SZ(B)+m) C.pb(0);
rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
L=n+1-L; B=T; b=d; m=1;
} else {
ll c=mod-d*powmod(b,mod-2)%mod;
while (SZ(C)<SZ(B)+m) C.pb(0);
rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
++m;
}
}
return C;
}
int gao(VI a,ll n) {
VI c=BM(a);
c.erase(c.begin());
rep(i,0,SZ(c)) c[i]=(mod-c[i])%mod;
return solve(n,c,VI(a.begin(),a.begin()+SZ(c)));
}
};
vector<ll> v;
int t,k;
ll dp[2050];
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d",&t);
while(t--){
scanf("%d%lld",&k,&n);
ll invk=powmod(k,mod-2);
memset(dp,0,sizeof(dp));
dp[0]=1;
v.clear();
v.push_back(1);
for(int i=1;i<=2*k;i++){
dp[i]=0;
for(int j=max(0,i-k);j<i;j++){
dp[i]=(dp[i]+dp[j])%mod;
}
dp[i]=(dp[i]*invk)%mod;
// printf("%lld
",dp[i]);
v.push_back(dp[i]);
}
ll ans;
if(n==-1){
ll invk_1=powmod(k+1,mod-2);
ans=2ll*invk_1%mod;
}else{
ans=linear_seq::gao(v,n);
}
printf("%lld
",ans);
}
return 0;
}
D Kth Minimum Clique
题意
给一个图,求第k小团(完全子图)。
分析
用bitset状压表示当前团,然后每次取出最小的,尝试加入一个点,满足该点和团内所有点都有边相连,用位运算实现,第k个取出的即为第k小团。
为了避免重复,标记每个团是由那个点扩大而来的。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=105;
typedef long long ll;
typedef bitset<N> bs;
int n,k;
ll a[N];
char s[N];
struct node{
int id;
ll val;
//当前团的状态
bs sta;
bool operator <(const node& rhs)const{
return val>rhs.val;
}
};
bs g[N];
bs t;
priority_queue<node> pq;
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);
}
for(int i=0;i<n;i++){
scanf("%s",s);
for(int j=0;j<n;j++){
g[i][j]=s[j]-'0';
}
}
if(k==1){
printf("0
");
return 0;
}
k--;
for(int i=0;i<n;i++){
t.reset();
t[i]=1;
pq.push(node{i,a[i],t});
}
while(!pq.empty()){
node tmp=pq.top();
pq.pop();
if(k==1){
printf("%lld
",tmp.val);
return 0;
}
k--;
for(int i=tmp.id+1;i<n;i++){
//如果一个点所连的所有点(g[i]刚好是当前团的所有点sta,那么加入i后仍然是团)
if((g[i]&tmp.sta)==tmp.sta){
bs now=tmp.sta;
now[i]=1;
pq.push(node{i,tmp.val+a[i],now});
}
}
}
printf("-1
");
return 0;
}
E MAZE
题意
给一个01矩阵,可以往左,右,下走,且只有0可以走,两种操作,修改某个点状态,询问从(1,a)到(n,b)的方案数。
分析
定义状态,(dp[i][j])表示从(i-1)行经过(g[i-1][j])走到(g[i][j])的方案数,因此(dp[i][j]=sum_{j=i}^{r}dp[i-1][j]+sum_{j=i-1}^{l}dp[i-1][j]),其中(l)和(r)表示(g[i-1][j]),左右连续0延伸的位置。
所以答案显然就是(dp[n+1][b]),因为(dp[n][b])只考虑了从(g[n-1][b])往下走的方案数。
显然从第一层到第二层状态的转移是一个线性递推的式子,中间可以构造一个矩阵,那么从第一层到第n+1层的状态显然就是n个矩阵的乘积了,所求结果就是((sum_{i=1}^nM_i)[a][b])。
修改的操作直接修改矩阵,然后线段树单点修改即可。
代码
#include <bits/stdc++.h>
using namespace std;
#define ls i<<1
#define rs i<<1|1
#define mid (l+r)/2
typedef long long ll;
const int N=50050;
const int M=12;
const ll mod=1e9+7;
int n,m,q,o,a,b;
int g[N][M];
char s[M];
struct Mat{
ll m[M][M];
void init(){
memset(m,0,sizeof(m));
}
};
Mat mMul(Mat a,Mat b){
Mat ans;
ans.init();
for(int i=0;i<M;i++){
for(int j=0;j<M;j++){
ans.m[i][j]=0;
for(int k=0;k<M;k++){
ans.m[i][j]+=(a.m[i][k]*b.m[k][j]%mod);
ans.m[i][j]%=mod;
}
}
}
return ans;
}
//线段树维护区间矩阵连乘
Mat pro[N*4];
void pushup(int i){
pro[i]=mMul(pro[ls],pro[rs]);
}
void build(int i,int l,int r){
if(l==r){
//先构造出每一层的转移矩阵
// pro[i].init();
for(int j=1;j<=m;j++){
//g上一行连续0的部分在转移矩阵中为1
for(int k=j;k<=m;k++){
if(g[l][k]==0){
pro[i].m[j][k]=1;
}else{
break;
}
}
for(int k=j;k>=1;k--){
if(g[l][k]==0){
pro[i].m[j][k]=1;
}else{
break;
}
}
}
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(i);
}
void update(int i,int l,int r,int a,int b){
if(l==r && a==l){
//单点更新,重置转移矩阵
pro[i].init();
for(int j=1;j<=m;j++){
//g上一行连续0的部分在转移矩阵中为1
for(int k=j;k<=m;k++){
if(g[a][k]==0){
pro[i].m[j][k]=1;
}else{
break;
}
}
for(int k=j;k>=1;k--){
if(g[a][k]==0){
pro[i].m[j][k]=1;
}else{
break;
}
}
}
return;
}
if(a<=mid){
update(ls,l,mid,a,b);
}else{
update(rs,mid+1,r,a,b);
}
pushup(i);
}
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++){
g[i][j]=(s[j]-'0');
}
}
build(1,1,n);
//对于每一个查询 答案就是(prod_{i-1}^n Mi)[a][b]
while(q--){
scanf("%d%d%d",&o,&a,&b);
if(o==1){
g[a][b]^=1;
update(1,1,n,a,b);
}else if(o==2){
printf("%lld
",pro[1].m[a][b]%mod);
}
}
return 0;
}
F Partition problem
题意
将2n个人分为两队,给定一个n*n的权值矩阵,定义总权值为两队各n个人分别的权值和,求最大总权值。
分析
直接暴力搜索,每加入一个就算贡献,不要等到全部分完再计算。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll g[30][30];
int n;
ll ans;
// vector<int> a,b;
int a[30],b[30];
int ac,bc;
void dfs(int idx,ll now){
if(ac==n && bc==n){
ans=max(ans,now);
return;
}
if(ac<n){
ll t=0;
for(int i=0;i<bc;i++){
t+=g[idx][b[i]];
}
now+=t;
a[ac++]=idx;
dfs(idx+1,now);
a[--ac]=0;
now-=t;
}
if(bc<n){
ll t=0;
for(int i=0;i<ac;i++){
t+=g[idx][a[i]];
}
now+=t;
b[bc++]=idx;
dfs(idx+1,now);
b[--bc]=0;
now-=t;
}
}
int main(void){
scanf("%d",&n);
for(int i=0;i<2*n;i++){
for(int j=0;j<2*n;j++){
scanf("%lld",&g[i][j]);
}
}
dfs(0,0);
printf("%lld
",ans);
return 0;
}
H Second Large Rectangle
题意
给一个01矩阵,求第二大的全1子矩阵。
分析
和最大的全1子矩阵类似,对于每一行,我们维护每个点能往上延伸的最大长度,然后就相当于经典的单调栈题目,给一些不同高度的柱子,求最大面积。
我们只需要用单调栈维护每个高度作为最小值能延伸到的最左和最右,然后扫一遍就能求出最大矩阵大小,但是这题要求的是次大,所以我们不能单独考虑最大的矩阵,也就是不能求出次大的最大子矩阵,而是要把每个最大子矩阵长减1,和宽减1再加入更新。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1050;
const int INF=0x3f3f3f3f;
int h[N],le[N],ri[N];
int n,m;
char s[N][N];
struct node{
int l,r,h;
bool operator <(const node &rhs)const{
if(l!=rhs.l){
return l<rhs.l;
}else{
if(r!=rhs.r){
return r<rhs.r;
}else{
return h<rhs.h;
}
}
}
};
map<node,int> mp;
stack<int> ss;
int mx,se;
void update(int x){
if(x>=mx){
se=mx;
mx=x;
}else if(x>se){
se=x;
}
}
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='1'){
h[j]++;
}else{
h[j]=0;
}
}
while(!ss.empty()){
ss.pop();
}
for(int j=1;j<=m;j++){
while(ss.size()>0 && h[j]<=h[ss.top()]){
ss.pop();
}
if(ss.size()>0){
le[j]=ss.top()+1;
}else{
le[j]=1;
}
ss.push(j);
}
while(!ss.empty()){
ss.pop();
}
for(int j=m;j>=1;j--){
while(ss.size()>0 && h[j]<=h[ss.top()]){
ss.pop();
}
if(ss.size()>0){
ri[j]=ss.top()-1;;
}else{
ri[j]=m;
}
ss.push(j);
}
for(int j=1;j<=m;j++){
if(h[j]==INF){
continue;
}
if(mp[{le[j],ri[j],h[j]}]){
continue;
}
mp[{le[j],ri[j],h[j]}]=1;
update(h[j]*(ri[j]-le[j]+1));
update(h[j]*(ri[j]-le[j]));
update((h[j]-1)*(ri[j]-le[j]+1));
}
}
printf("%d
",se);
return 0;
}