以后每周打牛客重现赛
递归组合数枚举。
先把所有人当成红队,再组合数枚举7个人移到白队,每次移动改变的竞争值可以在On的复杂度内算出。
#include
using namespace std;
typedef long long ll;
//typedef __int128 LL;
//typedef unsigned long long ull;
//#define F first
//#define S second
typedef long double ld;
typedef pair pii;
typedef pair pll;
typedef pair pdd;
const ld PI=acos(-1);
const ld eps=1e-9;
//unordered_mapmp;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
//#define a(i,j) a[(i)*(m+2)+(j)] //m是矩阵的列数
//pop_back()
const int seed=131;
const int M = 1e5+7;
/*
int head[M],cnt;
void init(){cnt=0,memset(head,0,sizeof(head));}
struct EDGE{int to,nxt,val;}ee[M*2];
void add(int x,int y,int z){ee[++cnt].nxt=head[x],ee[cnt].to=y,ee[cnt].val=z,head[x]=cnt;}
*/
int n,N;
int a[30][30];
bool vs[30];
ll ma;
ll ct=0;
void cal(int x,int num,ll ans)//判断第x个人选不选,已经选了num个人到白队,目前分组的竞争力是ans
{
if(num>n||num+(N-x+1)
每一行跑一遍单调栈,找出以i为底边的四边形,的最大子矩阵,并把记录的矩形 h*w,(w-1)*h,也记录下来。
否则第二大矩形会找不到。因为单调栈执行过程中会把第二大当成不优解给排除,由于高-1在下一行会算到,我们只记录宽-1形成的小矩形即可
#include
using namespace std;
typedef long long ll;
//typedef __int128 LL;
//typedef unsigned long long ull;
//#define F first
//#define S second
typedef long double ld;
typedef pair pii;
typedef pair pll;
typedef pair pdd;
const ld PI=acos(-1);
const ld eps=1e-9;
//unordered_mapmp;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
//#define a(i,j) a[(i)*(m+2)+(j)] //m是矩阵的列数
//pop_back()
const int seed=131;
const int M = 1007;
const int N =1e7;
/*
int head[M],cnt;
void init(){cnt=0,memset(head,0,sizeof(head));}
struct EDGE{int to,nxt,val;}ee[M*2];
void add(int x,int y,int z){ee[++cnt].nxt=head[x],ee[cnt].to=y,ee[cnt].val=z,head[x]=cnt;}
*/
char S[M][M];
int h[M][M];//第i行第j列,向上最多扩展多少。
int s[M],w[M];
int ar[N],sz;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",S[i]+1);
int ct=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(S[i][j]=='1')h[i][j]=h[i-1][j]+1,ct++;
else h[i][j]=0;
}
for(int i=1;i<=n;i++)
{
int p=0,ans=0;
memset(s,0,sizeof(s));
memset(w,0,sizeof(w));
for(int j=1;j<=m+1;j++)
{
if(h[i][j]>s[p])s[++p]=h[i][j],w[p]=1;
else {
int wi=0;
while(s[p]>h[i][j])
{
wi+=w[p];
ar[++sz]=wi*s[p];
if(wi>1)ar[++sz]=(wi-1)*s[p];
ans=max(ans,wi*s[p]);
p--;
}
s[++p]=h[i][j],w[p]=wi+1;
}
}
}
sort(ar+1,ar+1+sz);
if(sz<=1)puts("0");
else printf("%d\n",ar[sz-1]);
return 0;
}
/*
3 7
1010100
1111100
1111111
1 4
1110
*/
打表找规律或者推公式:
1:打表
#include
using namespace std;
int vs[100],nm[100];
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
srand((unsigned int)(time(NULL)));
int n;
scanf("%d",&n);
for(int i=1;i<=100000;i++)
{
int now=rand()%2;
memset(vs,0,sizeof(vs));
int tp=0,ct=1;
vs[0]=1;
while(ct
发现规律:到每个点的概率都相同,到0点概率为0
AC代码:
#include
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll qpow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1)ans=a*ans%mod;
a=a*a%mod;
b/=2;
}
return ans%mod;
}
int main()
{
int t;
ll ans=1;
scanf("%d",&t);
while(t--)
{
int n,m;
scanf("%d %d",&n,&m);
ll tp;
if(m==0)tp=0;else tp=n-1;
if(n==1)tp=1;
ans=(ans*qpow(tp,mod-2))%mod;
printf("%lld\n",ans);
}
return 0;
}
推公式:
假设答案是:f(n,m)。
1:首先f(n,0)=0.(n>1),走完其他点就直接停了
2:然后 f(n,i)=f(n,n-i+1). 因为左右走概率相同,最后停在前面一个的概率,肯定等于停在后面一个的概率(注意它是个环)。
3:最后,f(n,i)=f(n,(i+n-1)%n)+f(n,(i+1)%n)/2,即停在一个点的概率等于停在其前面一个点的概率+后面一个点的概率除以二。
因为:
最后停在点i。上一步必定是在i+1,或者i-1.
假设上一步是i+1,如果走完除了i点以外的所有点,且最后一步是i+1的概率是x1。那么在这x1概率中,有1/2的概率走向i点,1/2的概率到i+2(不会再回到i+1点,最后一步到i+1所有的概率已经是x了)所以最后一步停在i 点的概率:p+=1/2*x。
同理 p+=1/2*x2(最后一步停在i-1,且已经走完除了i的所有点的概率)
所以3结论成立。
由2、3可推出f(n,i) 任取 (1
所以得出:
f(1,0)=1;
f(n,m)=1/n;
f(n,0)=0;
最大集团数2^100.
直接求第k小不好求,枚举出集团数会T。
求第K小,一般可以看看能不能从第一小开始递推。
我们发现:最小的是空集。
然后往空集这个集团加一个点。
得到的集团中选个最小的(此时的集团一定是第二小)再往这个集团加一个可行点。
然后再取最小的集团(此时的集团一定是第三小)。
当前最小权值集团,一定是第i小,i为执行次数+1.
新的集团一定要通过当前集团一个新的点得到。而点的权值为正数。所以当前最小一定是第i小。
1.取最小集团这个操作明显可以用优先队列来做。
2.而判断一个点能否可以加入一个集团里,为了On的判断出来,我们可以用bitset进行维护。
bitset中1代表集团中包含的点。判断某点能否加入集团中,只需判断,这个集团的点是否都在这个点能连的点中。
所以读入连边时直接用bitset维护即可。
3.为了防止重复我们可以记录每个集团点编号最大的,加点时只能选择id更大的点加入,刚好是组合型枚举
#include
using namespace std;
typedef long long ll;
ll w[110];
struct node{
ll val;
int lst;
bitset<101>s;
bool operator <(const node &r)const{
return val>r.val;
}
};
priority_queueq;
bitset<101>e[101];
char S[101];
int main()
{
//freopen("3.in","r",stdin);
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
for(int i=1;i<=n;i++){
scanf("%s",S+1);
for(int j=1;j<=n;j++)
if(S[j]=='1')e[i][j]=1;else e[i][j]=0;
}
if(k==1){
printf("0\n");return 0;
}
bitset<101>p;
q.push(node{0,0,p});
int nm=0,ct=0;
//由于优先队列里都是正数,
//任意数再加上一个正数必然大于当前存的权值最小的集团的权值
//。所以堆头一定是当前最小权值集团
while(!q.empty())
{
node tp=q.top();q.pop();
nm++;
if(nm==k)
{
printf("%lld\n",tp.val);
return 0;
}
bool f=false;
for(int i=tp.lst+1;i<=n;i++)//从当前点后面的点进行选择,去重
{
bitset<101>now(tp.s);
if((e[i]&now)==now)//这个点可以加入tp这个小集团里
{
now[i]=1;
q.push(node{tp.val+w[i],i,now});
}
}
}
printf("-1\n");
return 0;
}
/*
5 13
545656160 714825755 534642371 950076512 270441846
00111
00110
11011
11101
10110
1484718883
*/
https://blog.csdn.net/yiqzq/article/details/96635574
学习这个大佬的思路
https://www.cnblogs.com/Yinku/p/11220848.html
抄了这个大佬的BM板子
这题就结束了。。。。瑟瑟发抖
思路蛮简单的,主要是k=-1时不好做。
https://www.zhihu.com/question/336062847?utm_source=qq&utm_medium=social&utm_oi=1017107436351676416
自行移步知乎进行学习。。考场上还是靠直觉或者打表吧。
下面说下我的比较通俗的理解
在足够长时间之后,平均每次前进k+1/2步,因此平均停留次数就是2/(k+1);
或者更简单的说,无穷大后,每(k+1)/2个点会到达一次。即最后到达的点是 :(k+1)/2,(k+1)/2 *2,(k+1)/2*3……
而无穷大的终点落在在某个我们达到点的概率是1/[(k+1)/2]=2/(k+1);(相当于终点不固定随机定)
#include
using namespace std;
#define rep(i,a,n) for (int i=a;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 vector VI;
typedef long long ll;
typedef pair 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,k;
namespace linear_seq {
const int N=10010;
ll res[N],base[N],_c[N],_md[N];
vector 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<=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)v;
int nCase;
scanf("%d", &nCase);
while(nCase--){
scanf("%lld%lld",&k, &n);
memset(f,0,sizeof(f));
v.clear();
f[1]=1;v.push_back(f[1]);
for(int i=2;i<=k*2;i++)
{
for(int j=max(1ll,i-k);j
dp思路:
dp[i][j]:经过(i-1,j)到达(i,j)的方案数。
如果不带修改,从(1,a)->(n,b). 初值:dp[1][a]=1; 输出dp[n+1][b].(只有这样才能保证最后一行也会有贡献)
转移:dp[i][j]=sum(dp[i-1][L],dp[i-1][R]). L,R为:(i-1,j)左边0的位置,右边0的位置。如果i-1,j为1,则dp[i][j]也为0(即不可能从i-1,j转移到i,j)
然后上述转移可以看成矩阵乘法:
比如i-1行为:011011
则dp[i][0],dp[i][1]……dp[i][m]=
0 | 0 | 0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 | 0 | 0 |
0 | 1 | 1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 1 |
0 | 0 | 0 | 0 | 1 | 1 |
*{dp[i-1][0],dp[i-1][1]……dp[i-1][m]}.
为什么要转化成矩阵乘法?
因为可以用线段树维护,这样更新查询就非常快的做出来了!!
#include
using namespace std;
typedef long long ll;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
const int M = 5e4+7;
const int mod=1e9+7;
int n,m,q;
char s[M][15];
struct node{
ll ma[10][10];
}tr[M<<2],P;
node mul(node a,node b)
{
node tp={0};
for(int i=0;i<=9;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
tp.ma[i][j]=(tp.ma[i][j]+a.ma[i][k]*b.ma[k][j]%mod)%mod;
return tp;
}
void tb(int o,int x)
{
tr[o]={0};
int pr=0;
for(int k=0;km)tp=mul(tp,qu(rs,m+1,r,x,y));
return tp;
}
int main()
{
for(int i=0;i<=9;i++)
for(int j=0;j<=9;j++)
if(i==j)P.ma[i][j]=1;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
bd(1,1,n);
while(q--)
{
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op==1)
{
if(s[l][r]=='0')s[l][r]='1';else s[l][r]='0';
up(1,1,n,l);
}
else
{
node tp=qu(1,1,n,1,n);
node a={0};
a.ma[0][l-1]=1;
a=mul(a,tp);
printf("%lld\n",a.ma[0][r-1]);
}
}
return 0;
}
JSubarray |
核心思想:合并有用区间。
虽然有1e9个数,但很容易想到:有一些-1是永远也用不上的
我们利用两个dp来预处理,找到有用的区间。
对区间的前缀和进行找正序对操作(即树状数组经典题)
然鹅树状数组会T。
然后就到了这题比较难的地方了
对于一个有效区间[L,R]
所有区间和最多3e7。
我们只能用On的算法去跑(现场赛的机器我感觉加个log也无所谓,牛客可惜了)
这题跟经典逆序对题目有点不一样:每次前缀和数值变化最多1,这就意味着我们可以开一个数组存数字出现次数,每次往后遍历,正序对数只会改变一个数字的出现次数。
具体看代码即可。
#include
#include
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int M=3e7+15;
int n;
int l[N],r[N],f[N],b[N];
//f[i] 以第i段1区间的右端点为右端点的区间中,区间和最大的是多少
//b[i] 以第i段1区间的左端点为左端点的区间中,区间和最大的是多少
int ct[M],nm1,nm2,sum[M];
const int base =1e7+3;
//ct[i] 在当前有效区间内,前缀和数值中,i-base已经出现的次数
//nm2[i] 在当前有效区间内,处理到第i位时,i位之前的数有多少数不大于第i位数
//nm1[i] 在当前有效区间内,处理到第i位时,i-1位之前的数有多少数不大于第i-1位数
//sum[i] 在当前有效区间内,前缀和。
int main()
{
//freopen("12.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&l[i],&r[i]),l[i]++,r[i]++;
ll pr=0;
l[n+1]=r[n+1]=2e9;
for(int i=1;i<=n;i++)
{
f[i]=max(f[i-1]-(l[i]-r[i-1]-1),0)+r[i]-l[i]+1;
int j=n-i+1;
b[j]=max(b[j+1]-(l[j+1]-r[j]-1),0)+r[j]-l[j]+1;
// cout<=l[pos+1]-r[pos]-1)pos++,ans+=r[pos]-l[pos]+1;//判断关系不要弄错,左边的右端点往左扩展的,和右边左端点往右扩展的,能否把中间的-1掩盖住
// cout<sum[p-1])nm2=nm1+ct[sum[p-1]+base];
else nm2=nm1-ct[sum[p]+base];
pr+=nm2;
ct[sum[p]+base]++;
// cout<