T1 permut
题目描述
求由1到n 一共n个数字组成的所有排列中,逆序对个数为k的有多少个
输入格式
第一行为一个整数T,为数据组数。
以下T行,每行两个整数n,k,意义如题目所述。
输出格式
对每组数据输出答案对10000 取模后的结果
Sample Input
1
4 1
Sample Output
3
数据范围及约定
对于30% 的数据:满足n<=12
对于所有数据:满足n<=1000,k<=1000,T<=10
一眼动归无疑。类似这种的dp做过很多遍了。枚举到 i 时,我们假设 i-1 的状态已经推出来了,那么考虑 i 。因为 i 肯定是整个序列最大的,所以如果将 i 插到 i-1 序列的最后,逆序对个数不会增加;如果插在倒数第一个前,那么逆序对个数要加 1 …………。综上所述,我们设 f[i][j] 表示前 i 个数构成 j 个逆序对的数列个数,那么由上得:f[i][j]=f[i-1][j]+f[i-1][j-1]+…+f[i-1][0]。值得注意的是,如果j>i-1,也就是说 i-1 无法提供 j 个逆序对,后面的就不能加上,因此要减去 f[i-1][j-i]…f[i-1][0]。考虑到时间复杂度的问题,我们可以定义一个 sum 数组来储存上面那一串。
代码:
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int f[1001][1001];
int sum[1001][1001];
inline int read()
{
static int r,sign;
static char c;
r=0,sign=1;
do c=getchar();while(c!='-'&&(c<'0'||c>'9'));
if(c=='-')sign=-1,c=getchar();
while(c>='0'&&c<='9')r=r*10+(int)(c-'0'),c=getchar();
return sign*r;
}
int main()
{
freopen("permut.in","r",stdin);
freopen("permut.out","w",stdout);
int t=read();
while(t--)
{
memset(f,0,sizeof f);
memset(sum,0,sizeof sum);
int n,k;
n=read();
k=read();
f[1][0]=1;
for(int i=0;i<=k;i++)
sum[1][i]=1;
for(int i=1;i<=n;i++)
{
f[i][0]=1;
sum[i][0]=1;
}
for(int i=2;i<=n;i++)
for(int j=1;j<=k;j++)
{
f[i][j]=sum[i-1][j];
if(j>i-1)
f[i][j]=((f[i][j]-sum[i-1][j-i])+10000)%10000;
sum[i][j]=(sum[i][j-1]+f[i][j])%10000;
}
printf("%d\n",f[n][k]);
}
return 0;
}
T2 beautiful
题目描述
一个长度为n 的序列,对于每个位置i 的数ai 都有一个优美值,其定义是:找到序列中最长的一段[l, r],满足l i r,且[l, r] 中位数为ai(我们比较序列中两个位置的数的大小时,以数值为第一关键字,下标为第二关键字比较。这样的话[l, r] 的长度只有可能是奇数),r - l+ 1 就是i 的优美值。
接下来有Q 个询问,每个询问[l, r] 表示查询区间[l, r] 内优美值的最大值。
输入格式
第一行输入n 接下来n 个整数,代表ai 接下来Q,代表有Q 个区间接下来Q 行,每行两个整数l, r(l r),表示区间的左右端点。
输出格式
对于每个区间的询问,输出答案。
Sample Input
8
16 19 7 8 9 11 20 16
8
3 8
1 4
2 3
1 1
5 5
1 2
2 8
7 8
Sample Output
7
3
1
3
5
3
7
3
数据范围及约定
对于30% 的数据:满足n,Q<=50
对于70% 的数据:满足n,Q<=2000
对于所有数据:满足n<=2000,Q<=100000,ai<=200
表示恶补了一周的动态规划,我的最强的暴力都码不来了…T3的暴力也没码对…本题正解可以用线段树或者RMQ(表示已忘…),但数据较水,枚举第 i 个数的两边,判断第 i 个数是否为中位数即可。时间复杂度O(n²)。
代码:
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int L[4005],R[4005],beauti[2005],val[2005];
inline int read()
{
static int r,sign;
static char c;
r=0,sign=1;
do c=getchar();while(c!='-'&&(c<'0'||c>'9'));
if(c=='-')sign=-1,c=getchar();
while(c>='0'&&c<='9')r=r*10+(int)(c-'0'),c=getchar();
return sign*r;
}
int main()
{
freopen("beautiful.in","r",stdin);
freopen("beautiful.out","w",stdout);
int n;
n=read();
for(int i=1;i<=n;i++)
val[i]=read();
for(int i=1;i<=n;i++)
{
memset(R,255,sizeof R);
memset(L,255,sizeof L);
L[n]=R[n]=0;
int cnt=0;
for(int j=i-1;j>=1;j--)
{
if(val[j]>val[i])cnt++;
else cnt--;
L[n+cnt]=i-j;
}
cnt=0;
for(int j=i+1;j<=n;j++)
{
if(val[j]>=val[i])cnt++;
else cnt--;
R[n+cnt]=j-i;
}
for(int j=1-i;j<=i-1;j++)
if(L[j+n]>=0&&R[n-j]>=0)
//左边小于的==右边大于的||右边小于的==左边大于的
beauti[i]=max(beauti[i],L[n+j]+R[n-j]+1);
}
int q=read();
while(q--)
{
int l,r;
l=read();
r=read();
int ans=0;
for(int i=l;i<=r;i++)//正解此处要用线段树或RMQ
ans=max(ans,beauti[i]);
printf("%d\n",ans);
}
}
T3 subset
题目描述
一开始你有一个空集,集合可以出现重复元素,然后有Q 个操作
1.add s
在集合中加入数字 s 。
2.del s
在集合中删除数字 s 。保证 s 存在
3.cnt s
查询满足 a&s=a 条件的 a 的个数
输入格式
第一行一个整数Q接下来Q行,每一行都是3个操作中的一个
输出格式
对于每个 cnt 操作输出答案
Sample Input
7
add 11
cnt 15
add 4
add 0
cnt 6
del 4
cnt 15
Sample Output
1
2
2
数据范围及约定
对于30% 的数据满足:1<=n<=1000
对于100% 的数据满足:1<=n<=200000,0<=s<=2^16
暴力码错了……
正解非常的巧妙,让我叹为观止。
记一个数组 gg[pre][nex],表示前面 8 位是 pre,后面 8 位是 nex 的数的子集的数字的个数。那么对于每个 add 和 del 操作都可以最多 2^8 时间枚举 nex 更新a 数组。对于 cnt 操作,最多 2^8 枚举 pre,时间复杂度O(n×2^8)。
代码:
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int gg[256][256];
inline int read()
{
static int r,sign;
static char c;
r=0,sign=1;
do c=getchar();while(c!='-'&&(c<'0'||c>'9'));
if(c=='-')sign=-1,c=getchar();
while(c>='0'&&c<='9')r=r*10+(int)(c-'0'),c=getchar();
return sign*r;
}
int main()
{
freopen("subset.in","r",stdin);
freopen("subset.out","w",stdout);
int n=read();
while(n--)
{
char a,b,c,d;int t,add=1;
scanf("%c%c%c%c",&a,&b,&c,&d);
t=read();
if(a=='d')
add=-1;
if(a!='c')
{
int pre=t>>8,nex=t&255,com=255^nex;
//pre表示这个数前8位,nex表示后8位,com表示后8位的补集
gg[pre][nex]+=add;
for(int i=com;i!=0;i=(i-1)&com/*枚举子集*/)
//前8位为pre的后8位的子集
gg[pre][i|nex]+=add;
}
else
{
int pre=t>>8,nex=t&255;
int ans=gg[0][nex];
for(int i=pre;i!=0;i=(i-1)&pre/*枚举包含它的集合*/)
ans+=gg[i][nex];
printf("%d\n",ans);
}
}
return 0;
}
逆天的读入优化。(没卡常,也没快多少)。