目录
A 、Eddy Walker (打表找规律+数学)
D 、Kth Minimum Clique(第k小团)
E 、MAZE(DP+线段树+矩阵)
H 、Second Large Rectangle(单调栈 || DP)
J 、Subarray (前缀和预处理+优化)
A 、Eddy Walker
题意:
有n个点的环,初始位置在0,可以随机向前走或向后走,问n个位置都走过,并且最后停在m的概率,最后输出前i种情况的概率的乘积。
分析:
打表找规律,根据打表可以大致看出除了0点外,其他点的概率都为
同时还要特判比如n=1的时候概率肯定是1,n>1且m==0明显不可能实现,所以概率一定为0.
打表代码:
#include<bits/stdc++.h>
using namespace std;
bool vis[10];
int num[10];
int main(){
int n;
scanf("%d",&n);
srand((unsigned int)time(NULL));
for(int i=0;i<1e5;i++){
int pos=0,cnt=1;
memset(vis,0,sizeof vis);
vis[0]=1;
while(cnt<n){
int step=rand()%2?1:-1;
pos=(pos+step+n)%n;
if(!vis[pos]) {
vis[pos]=1;
cnt++;
}
if(cnt==n){
num[pos]++;
}
}
}
for(int i=0;i<n;i++){
printf("%d\n",num[i]);
}
return 0;
}
ac代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll quick_pow(ll a,ll b){
ll ans=1ll;
while(b){
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int main(){
int T;
scanf("%d",&T);
ll ans=1;
while(T--){
int n,m;
scanf("%d%d",&n,&m);
if(n==1&&m==0){
printf("%lld\n",ans);
}else if(n>1&&m==0){
ans=0;
printf("%lld\n",ans);
}else{
ans=ans*quick_pow(n-1,mod-2)%mod;
printf("%lld\n",ans);
}
}
return 0;
}
D 、Kth Minimum Clique
题意:
给定一个无向图领接矩阵,每个点有权值,找出权值第k小的团的权值(一个团就是图的一个完全子图)
分析:我们可以知道最小的团就是那个空的完全图,
也就是0阶完全子图,由此启发,我们可以从这个最小的团生成第二小,第三小...的团,因为第二小,第三小...的团是由这个空团加入点生成的,这里很明显是一种广度优先搜索的思路
但是,我们如何使得每次出队的团都是按照第n小,第n+1小这样的顺序来呢?很明显,由于每个点有权值,而团的大小的定义是点的权值和大小,并不是点的个数,因此这个地方,我们用优先队列实现,
小团优先出队列,这样将可以保证出队的团的顺序是按照团的大小顺序,由小到大,因为每个团生成新的团一定会更大,所以第n次出队时堆顶的团一定是第n小的团,模拟这个BFS过程我们就可以得到第k小的团。
因为n较小,所以可以用bitset进行优化判断点是否连接。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
bitset<maxn> g[maxn];
typedef long long ll;
struct node{
int id;
ll w;
bitset<maxn> clique;
bool operator < (const node &p) const {
return w>p.w;
}
};
ll val[maxn];
ll ans;
ll bfs(int n,int k){
priority_queue<node> q;
node now;
now.id=now.w=0;
q.push(now);
while(!q.empty()){
now=q.top();
q.pop();
if((--k)==0) return now.w;
for(int i=now.id+1;i<=n;i++){
if((g[i]&now.clique)==now.clique) {
node next;
next.id=i;
next.w=now.w+val[i];
next.clique=now.clique;
next.clique[i]=1;
q.push(next);
}
}
}
return -1ll;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&val[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int a;
scanf("%1d",&a);
g[i][j]=a;
}
}
printf("%lld\n",bfs(n,k));
return 0;
}
E 、MAZE
题意:
n * m的矩阵,为0表示可以走,1不可以走。规定每走一步只能向下、向左、向右走。现给定两种操作:
1 x y表示翻转坐标(x,y)的0、1。
2 x y表示从(1,x)走到(n,y)有几种走法
分析:
定义状态, 表示从行经过走到的方案数,因此,其中和表示,左右连续0延伸的位置。
所以答案显然就是,因为只考虑了从往下走的方案数。
显然从第一层到第二层状态的转移是一个线性递推的式子,中间可以构造一个矩阵,
那么从第一层到第n+1层的状态显然就是n个矩阵的乘积了,所求结果就是
修改的操作直接修改矩阵,然后线段树单点修改即可。
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
const int maxn=5e4+5;
const int maxm=12;
int n,m,q;
char mp[maxn][maxm];
int maz[maxn][maxm];
struct Matrix{
ll a[maxm][maxm];
Matrix(){
mm(a,0);
}
Matrix operator * (const Matrix& p) {
Matrix ans;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=m;k++){
ans.a[i][j]=(ans.a[i][j]+a[i][k]*p.a[k][j]%mod)%mod;
}
}
}
return ans;
}
}sum[maxn<<2];
Matrix Get(int k){
Matrix ans;
for(int i=1;i<=m;i++){
for(int j=i;j<=m;j++){
if(maz[k][j]) break;
ans.a[i][j]=ans.a[j][i]=1;
}
}
return ans;
}
void push_up(int i){
sum[i]=sum[i<<1]*sum[i<<1|1];
}
void build(int i,int l,int r){
if(l==r){
sum[i]=Get(l);
return ;
}
int mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
push_up(i);
}
void update(int i,int l,int r,int x){
if(l==r){
sum[i]=Get(l);
return ;
}
int mid=l+r>>1;
if(mid>=x){
update(i<<1,l,mid,x);
}else update(i<<1|1,mid+1,r,x);
push_up(i);
}
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++){
scanf("%s",mp[i]+1);
for(int j=1;j<=m;j++){
maz[i][j]=mp[i][j]-'0';
}
}
build(1,1,n);
while(q--){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==1){
maz[x][y]^=1;
update(1,1,n,x);
}else{
printf("%lld\n",sum[1].a[x][y]);
}
}
return 0;
}
F、 Partition problem
题意:
总共有2n个人,任意两个人之间会有一个竞争值,现在要你将其平分成两堆,使得最大。
分析:
枚举第i个人时,前面已经有cnt1个人分进了A,cnt2个人分进了B中,则
- 如果cnt1<n,那么i可以放进A中,在放进去的时候将sum加上i与前cnt2个人的竞争值;
- 如果cnt2<n,那么i可以放进B中,在放进去的时候将sum加上i与前cnt1个人的竞争值。
在cnt1,cnt2都等于n时将sum与ans进行取max即可,复杂度为
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll v[30][30];
ll t1[30],t2[30];
ll ans;
int n;
void dfs(int pos,int cnt1,int cnt2,ll res){
if(cnt1==cnt2&&cnt1==n){
ans=max(ans,res);
return ;
}
ll tmp=0;
if(cnt1<n){
for(int i=0;i<cnt2;i++){
tmp+=v[pos][t2[i]];
}
t1[cnt1]=pos;
dfs(pos+1,cnt1+1,cnt2,res+tmp);
}
tmp=0;
if(cnt2<n){
for(int i=0;i<cnt1;i++){
tmp+=v[pos][t1[i]];
}
t2[cnt2]=pos;
dfs(pos+1,cnt1,cnt2+1,res+tmp);
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n*2;i++){
for(int j=0;j<2*n;j++){
scanf("%lld",&v[i][j]);
}
}
dfs(0,0,0,0);
printf("%lld\n",ans);
return 0;
}
H 、Second Large Rectangle
有个跟这个题相关的题,可以先看这个。
https://blog.csdn.net/doc_sgl/article/details/11832965
那么这个题就转化为一个单调栈或者DP问题了。注意DP的时候要去重。
单调栈写的时候要注意将长减一或者高减一进行计算。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int g[maxn][maxn];
int mx,dx,n,m;
int l[maxn],r[maxn];
void solve(int f[]){
stack<int>s;
f[m+1]=-1;
int ans;
for(int i=1;i<=m+1;i++){
if(s.empty()||f[i]>f[s.top()]){
s.push(i);
}
else if(f[i]<f[s.top()]){
while(!s.empty()&&f[i]<f[s.top()]){
int temp=(i-s.top())*f[s.top()];
if(temp>mx){
dx=mx;
mx=temp;
}else if(temp>dx){
dx=temp;
}
temp=(i-s.top()-1)*f[s.top()];
if(temp>mx){
dx=mx;
mx=temp;
}else if(temp>dx){
dx=temp;
}
temp=(i-s.top())*(f[s.top()]-1);
if(temp>mx){
dx=mx;
mx=temp;
}else if(temp>dx){
dx=temp;
}
ans=s.top();
s.pop();
}
s.push(ans);
f[ans]=f[i];
}
}
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
memset(g,0,sizeof g);
mx=dx=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char c;
scanf(" %c",&c);
if(c=='1') g[i][j]=g[i-1][j]+1;
}
}
for(int i=1;i<=n;i++){
solve(g[i]);
}
printf("%d\n",dx);
}
return 0;
}
/*
3 5
10111
10111
10111
3 3
001
001
111
3 3
001
011
011
1 3
111
*/
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
#define pii pair<int,int>
#define pid pair<double,double>
#define pil pair<long long,long long>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rush() int T;scanf("%d",&T);while(T--)
#define sc(a) scanf("%d",&a)
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
//#define gc getchar
char buf[1<<21],*p1=buf,*p2=buf;
inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline int read(){
int ret=0,f=0;char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){ret=ret*10+c-48;c=gc();}
if(f)return -ret;return ret;
}
const int maxn=1e3+10;
int L[maxn],R[maxn];
int n,m;
char s[maxn][maxn];
int h[maxn][maxn];
int vis[maxn][maxn];
int num[maxn];
int mx,ans;
void solve(int t){
rep(i,1,m) L[i]=R[i]=i;
rep(i,2,m){
int now=i;
while(now>1&&h[t][i]<=h[t][now-1]) now=L[now-1];
L[i]=now;
}
for(int i=m-1;i>=1;i--){
int now=i;
while(now<m&&h[t][i]<=h[t][now+1]) now=R[now+1];
R[i]=now;
}
rep(i,1,m){
if(vis[L[i]][R[i]]==t) continue;//去重
int tmp=(R[i]-L[i]+1)*(h[t][i]);
if(tmp>mx){
ans=mx;
mx=tmp;
}else if(tmp>ans){
ans=tmp;
}
tmp=(R[i]-L[i])*(h[t][i]);
if(tmp>mx){
ans=mx;
mx=tmp;
}else if(tmp>ans){
ans=tmp;
}
tmp=(R[i]-L[i]+1)*(h[t][i]-1);
if(tmp>mx){
ans=mx;
mx=tmp;
}else if(tmp>ans){
ans=tmp;
}
vis[L[i]][R[i]]=t;
}
}
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d",&n,&m);
rep(i,1,n){
rep(j,1,m){
scanf(" %c",&s[i][j]);
if(s[i][j]=='1') h[i][j]=h[i-1][j]+1;
}
}
rep(i,1,n) solve(i);
printf("%d\n",ans);
return 0;
}
J 、Subarray
题意:
长度为1e9的(1,−1)序列,下标从0到1e9−1,已知有n个区间为1,其他为−1, 问存在多少个区间的和>0
保证
分析:
1、可能作为区间端点的点个数最多为3e7
2、f[i]表示以第i个区间右端点为答案右端点的最大区间和
3、g[i]表示以第i个区间左端点为答案左端点的最大区间和
4、
5、
6、如果 ,说明答案的左右端点可以跨越,那么把这些合并考虑
7、搞完上面,问题就变成了给你一个长度不超过3e7的(1,−1)序列,问有多少区间和大于1
参考dl的博客:
https://www.cnblogs.com/FST-stay-night/p/11218660.html
https://www.cnblogs.com/Yinku/p/11221494.html
#include<bits/stdc++.h>
#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=3e7+5;
const int maxm=1e6+5;
int l[maxm],r[maxm];
int f[maxm]; //表示以第i个区间右端点为答案右端点的最大区间和
int g[maxm]; //表示以第i个区间左端点为答案左端点的最大区间和
int sum[maxn],b[maxn],c[maxn];
int main(){
#ifdef io
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
}
f[1]=r[1]-l[1]+1;
for(int i=2;i<=n;i++){
f[i]=max(0,f[i-1]-(l[i]-r[i-1]-1))+r[i]-l[i]+1;
}
g[n]=r[n]-l[n]+1;
for(int i=n-1;i>=1;i--){
g[i]=max(0,g[i+1]-(l[i+1]-r[i]-1))+r[i]-l[i]+1;
}
int i=1,base=1e7;
ll ans=0;
while(i<=n){
int j=i+1;
while(j<=n&&g[j]+f[j-1]>=l[j]-r[j-1]-1){
j++;
}
j--;
int left=max(0,l[i]-g[i]),right=min(1000000000-1,r[j]+f[j]);
int t=i,mi=inf,mx=0;
sum[0]=0;
for(int k=left;k<=right;k++){
if(k>=l[t]&&k<=r[t]){
sum[k-left+1]=sum[k-left]+1;
}else{
sum[k-left+1]=sum[k-left]-1;
}
if(k==r[t]) t++;
mi=min(mi,sum[k-left+1]+base);
mx=max(mx,sum[k-left+1]+base);
b[sum[k-left+1]+base]++;
}
for(int k=mx-1;k>=mi;k--){
b[k]+=b[k+1];
}
ans+=b[base+1];
for(int k=left;k<=right;k++){
t=sum[k-left+1]+base;
b[t+1]-=c[t+1];
c[t]+=c[t+1]+1;
c[t+1]=0;
ans+=b[t+1];
}
for(int k=mi;k<=mx;k++){
b[k]=0;c[k]=0;
}
i=j+1;
}
printf("%lld\n",ans);
return 0;
}