序列
给定N、A、B,构造一个长度为N的排列。
使得其最长上升子序列长度为A,最长下降子序列长度为B
n<=
观察N=AB的情况,可以发现是
这样子的,组数是A,个数是B。
于是AB<N时或N<A+B-1无解.
其他时候每组的B不塞满就可以了。
这题重点并不是怎么想而是怎么写!!!!!这种模拟题有一种常见的减少思维量的技巧,就是一边做一边 ,用剩下的 去思考可以省事很多。(一位代码写炸的人的自我反省)
代码
#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 1000005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
LL res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
int n,a,b,T;
int main(){
T=read();
while(T--){
n=read(); a=read(); b=read();
if(n>a*b || n<a+b-1){puts("No"); continue;}
puts("Yes");
while(n){
int x=min(a,n-b+1);
for(int i=n-x+1;i<=n;i++) printf("%d ",i);
b--;
n-=x;
}
puts("");
}
return 0;
}
购物
visit_world 有一个商店,商店里卖N个商品,第i 个的价格为a[i]。我们称一个正整数K 是美妙的,当且仅当我们可以在商店里选购若干个商品,使得价格之和落在区间 [K,2K]中。
问:有多少个美妙的数。
考虑一个价格
它能影响到
假如现在有了n个答案区间,那么每个答案区间的右端点+
就是更新区间了。这样其实这样就能做了,但是我们继续将这道奇技淫巧。
假如之前的区间连续。首先分类讨论a[i],设现在处理到了
表示最右端点。
如果
<
,
所以新加一个
的时候直接
就行了,也就是突出来的长度。
如果
,那么
,也就是会空出来一个长度,剪掉就行。
如果不连续,首先那个区间的右端点一定是某个前缀和设
,那么下一段的开始假设是
,那么新的靠左区间的右端点是
下一个是
大,碰到时
即
那么我们只要让
就可以保证永远碰不到了。那么就
排序(可以理解成一跑跑太远)
代码
#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 1000005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
LL res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
int n,m,a[maxn];
LL sum[maxn];
LL ans;
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read();
sort(a+1,a+n+1);
for(int i=1;i<=n;i++) sum[i]=a[i]+sum[i-1];
for(int i=2;i<=n;i++){
if((a[i]+1>>1)>sum[i-1]) ans-=(a[i]+1>>1)-sum[i-1]-1;
}
ans+=sum[n]-(a[1]+1>>1)+1;
printf("%lld\n",ans);
return 0;
}
计数
给定一棵先序遍历序是编号序的数,给定M个条件要求u的中序遍历小于v,求方案数n<=400,m<=10e3
树的遍历
先序遍历的性质是一个子数的先序遍历序号是连续的,且第一个为根,考虑DP,枚举两棵树的先序遍历分界线,也就是斯特林数。那么思考一下带条件的斯特林数。
中序遍历的性质是一个左子树的所有小于根小于右子树的所有,因为左右子树和根都是一段连续的区间,两个区间的关系可以使用二维前缀和标记。于是记忆化搜索,枚举分界点,注意,左子树或右子树为空的情况需要考虑进去。
代码
#include <bits/stdc++.h>
#define maxn 405
#define mod 1000000007
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
LL read(){
LL res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
int T,n,m;
LL f[maxn][maxn],sum[maxn][maxn];
bool A(int a,int c,int b,int d){
if(a>c || b>d) return 0;
return sum[c][d]+sum[a-1][b-1]-sum[a-1][d]-sum[c][b-1]>0;
}
LL DFS(int x,int y){
if(~f[x][y]) return f[x][y];
LL res=0;
if(x>=y) return 1;
for(int i=x;i<=y;i++){
if(!A(i+1,y,x+1,i)&&!A(x,x,x+1,i)&&!A(i+1,y,x,x)) res=(res+DFS(x+1,i)*DFS(i+1,y)%mod)%mod;
}
return f[x][y]=res;
}
int main(){
T=read();
while(T--){
n=read(); m=read();
memset(f,-1,sizeof f);
memset(sum,0,sizeof sum);
for(int i=1;i<=m;i++){
sum[read()][read()]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
}
printf("%lld\n",DFS(1,n));
}
return 0;
}
Matrix
给定 和 ,
求
考虑每一个 和 会被 取到几次,因为 会向左下角沿一条路走到 ,系数就是路线的方案数。每一个位置会向下和向左走的步数是确定的,所以 和 的指数是确定的,于是就可以跑了,具体的见代码吧。
代码
#include <bits/stdc++.h>
#define maxn 200005
#define mod 1000000007
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL read(){
LL res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
LL Pow(LL x,LL k){
LL res=1;
while(k){
if(k&1) res=(res*x)%mod;
k>>=1;
x=(x*x)%mod;
}
return res;
}
LL n,a,b,ans,l[maxn],t[maxn],jc[maxn];
LL C(LL b,LL a){
return jc[b]*Pow(jc[a],mod-2)%mod*Pow(jc[b-a],mod-2)%mod;
}
int main(){
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
jc[0]=1;
for(int i=1;i<maxn;i++) jc[i]=jc[i-1]*i%mod;
n=read(); b=read(); a=read();
l[1]=read();
for(int i=2;i<=n;i++){
ans=(ans+(l[i]=read())*C(2*n-2-i,n-2)%mod*Pow(b,n-1)%mod*Pow(a,n-i)%mod)%mod;
}
t[1]=read();
for(int i=2;i<=n;i++){
ans=(ans+(t[i]=read())*C(2*n-2-i,n-2)%mod*Pow(a,n-1)%mod*Pow(b,n-i)%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
Graph
求包含1的最小环,不能走一下就回来。
考虑朴素的方法,把1拆成出点和入点,出点到入点的最短路就是答案了。然而这样不能避免走一下就回来的问题,考虑枚举,那么我们有两种方案。
一种是枚举中间的某条边,从1开始分别沿正边和反边跑,每次统计答案的时候看两条dis是不是从一个点出来的,如果不是就记ans,这样的效率是两个Dijstra
另一种是枚举开头的两个点,但是这样枚举的复杂度是
,那么我们可以把点平分成两部分
,这样是对于
的
对我们一边Dijstra统计完了他们的答案,考虑怎样分割使得所有不等的
对都被包括,二进制拆分,把每一位不同的拆成两部分这样就可以用
的效率完成枚举了总效率
代码
#include <bits/stdc++.h>
#define maxn 100005
#define MAXN 200005
#define LL long long
#define INF 1e9
using namespace std;
LL read(){
LL res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=c^48;
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
LL dis[maxn];
struct NODE{
int x,y;
bool operator < (const NODE &rhs)const{
return y>rhs.y;
}
};
struct EDGE{
int u,v,w,nxt;
}e[MAXN];
int cnt=1,head[maxn];
void add(int u,int v,int w){
e[++cnt]=(EDGE){u,v,w,head[u]};
head[u]=cnt;
}
priority_queue<NODE> Q;
void Dijstra(int s){
memset(dis,0x3f,sizeof dis);
Q.push((NODE){s,0}); dis[s]=0;
while(!Q.empty()){
NODE u=Q.top(); Q.pop();
if(u.y!=dis[u.x]) continue;
for(int i=head[u.x];~i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(dis[u.x]+w<dis[v]){
dis[v]=dis[u.x]+w;
Q.push((NODE){v,dis[v]});
}
}
}
}
int n,m,u[maxn],v[maxn],w1[maxn],w2[maxn];
LL ans=INF;
int main(){
n=read(); m=read();
for(int i=1;i<=m;i++){
u[i]=read(); v[i]=read(); w1[i]=read(); w2[i]=read();
}
int Log=log(n)/log(2)+1;
for(int s=0;s<=Log;s++){
cnt=0; memset(head,-1,sizeof head);
for(int i=1;i<=m;i++){
if(u[i]==1){
if((v[i]>>s)&1) add(v[i],n+1,w2[i]);
else add(1,v[i],w1[i]);
}
else if(v[i]==1){
if((u[i]>>s)&1) add(u[i],n+1,w1[i]);
else add(1,u[i],w2[i]);
}
else {add(u[i],v[i],w1[i]); add(v[i],u[i],w2[i]);}
}
Dijstra(1);
ans=min(ans,dis[n+1]);
cnt=0; memset(head,-1,sizeof head);
for(int i=1;i<=m;i++){
if(u[i]==1){
if(!((v[i]>>s)&1)) add(v[i],n+1,w2[i]);
else add(1,v[i],w1[i]);
}
else if(v[i]==1){
if(!((u[i]>>s)&1)) add(u[i],n+1,w1[i]);
else add(1,u[i],w2[i]);
}
else {add(u[i],v[i],w1[i]); add(v[i],u[i],w2[i]);}
}
Dijstra(1);
ans=min(ans,dis[n+1]);
}
printf("%lld\n",ans);
return 0;
}
circle
给定一张边权有正有负的有向图,求经过点数最小的正环
第一,正环非常不好搞,一般这种不好搞的东西考虑二分答案。
第二,跟点数有关的题目很难提前限制或者直接做,所以我们限定点数二分答案。
两种思路都指向二分答案。
于是倍增Floyd,二分即可。每一个值需要
求出,所以是
但是更快的方法是用类似倍增LCA的方法做一遍
因为我弱,所以我用的是
代码
#include <bits/stdc++.h>
#define maxn 305
using namespace std;
int n,m,e[10][maxn][maxn],ans[2][maxn][maxn];
inline int MX(int a,int b){
if(a>b) return a;
else return b;
}
bool check(int p){
int pos=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans[0][i][j]=(i==j?0:-1e9);
}
}
for(int s=0;(1<<s)<=p;s++){
if(p&(1<<s)){
pos^=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ans[pos][i][j]=-1e9;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(ans[pos^1][i][k]+e[s][k][j]>ans[pos][i][j]){
ans[pos][i][j]=ans[pos^1][i][k]+e[s][k][j];
}
}
}
}
}
}
for(int i=1;i<=n;i++){
if(ans[pos][i][i]>0) return 1;
}
return 0;
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int s=0;(1<<s)<=n;s++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
e[s][i][j]=(i==j?0:-1e9);
}
}
}
for(int i=1,x,y,a,b;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&a,&b);
e[0][x][y]=a; e[0][y][x]=b;
}
for(int s=1;(1<<s)<=n;s++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
if(e[s-1][i][k]+e[s-1][k][j]>e[s][i][j]){
e[s][i][j]=e[s-1][i][k]+e[s-1][k][j];
}
}
}
}
}
int l=2,r=n+1;
while(l<r){
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",l%(n+1));
}