T1
【问题描述】
迟到大王rsw喜欢V这个字母,因为V代表着victory,当一个数字,从左向右数的时候,没有出现过先递增,再递减的情况,就被称作Victory数。
也就是说,这个数字可以是递增的,也可以是递减的,也可以是先递减再递增的,这个过程中可以出现相邻数字相等的情况,但是,就是不能出现过先递增再递减的情况。
问题是:给定n,问:1~n之间有多少个victory数。
【输入格式】
一行,一个数字n。
【输出格式】
一行,一个数字,表示答案mod 1 000 000 007
【样例输入】
120
【样例输出】
119
【数据范围】
对于30%的数据,1<=n<=10^8
对于100%的数据,1<=n<=10^100
较裸的数位dp,刷表代码不好写,比赛的时候调了半天,不过听其他人说记忆搜好写。。。
dp[i][j][k][q]表示从大到小第i位选j,上升下降情况为k(用01表示),是否达到上界(q为01)的方案数。这里转移的时候需要注意,因为题目要求,所以上升的方法是不能再下降的,而下降中的方案可以上升。对于持平的情况,我们规定在下降的过程中持平继续算作下降,当下一位变为严格大于时,转为上升,之后的持平也算作上升。
整体细节较多,看代码。
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<math.h>
using namespace std;
int rw=1000000007;
char s[205];
int dp[200][10][2][2];
int len;
int main()
{
freopen("victory.in","r",stdin);
freopen("victory.out","w",stdout);
scanf("%s",s);
len=strlen(s);
for(int i=1;i<=s[0]-'0';i++)
{
if(i==s[0]-'0')
dp[0][i][1][1]=1;
else
dp[0][i][1][0]=1;
}
for(int i=1;i<len;i++)
{
for(int j=1;j<10;j++)
{
dp[i][j][1][0]=1;
}
}
for(int i=1;i<len;i++)
{
for(int j=0;j<10;j++)
{
for(int k=0;k<10;k++)
{
if(j>k)
{
if(j>s[i]-'0')
{
dp[i][j][0][0]+=(dp[i-1][k][0][0]+dp[i-1][k][1][0])%rw;
dp[i][j][0][0]%=rw;
}
else if(j==s[i]-'0')
{
dp[i][j][0][0]+=(dp[i-1][k][0][0]+dp[i-1][k][1][0])%rw;
dp[i][j][0][0]%=rw;
dp[i][j][0][1]+=(dp[i-1][k][0][1]+dp[i-1][k][1][1])%rw;
dp[i][j][0][1]%=rw;
}
else if(j<s[i]-'0')
{
dp[i][j][0][0]+=(dp[i-1][k][0][0]+dp[i-1][k][1][0])%rw;
dp[i][j][0][0]%=rw;
dp[i][j][0][0]+=(dp[i-1][k][0][1]+dp[i-1][k][1][1])%rw;
dp[i][j][0][0]%=rw;
}
}
else if(j==k)
{
if(j>s[i]-'0')
{
dp[i][j][0][0]+=dp[i-1][k][0][0]%rw;
dp[i][j][0][0]%=rw;
dp[i][j][1][0]+=dp[i-1][k][1][0]%rw;
dp[i][j][1][0]%=rw;
}
else if(j==s[i]-'0')
{
dp[i][j][0][0]+=dp[i-1][k][0][0];
dp[i][j][0][0]%=rw;
dp[i][j][1][0]+=dp[i-1][k][1][0];
dp[i][j][1][0]%=rw;
dp[i][j][0][1]+=dp[i-1][k][0][1];
dp[i][j][0][1]%=rw;
dp[i][j][1][1]+=dp[i-1][k][1][1];
dp[i][j][1][1]%=rw;
}
else if(j<s[i]-'0')
{
dp[i][j][0][0]+=(dp[i-1][k][0][0]+dp[i-1][k][0][1])%rw;
dp[i][j][0][0]%=rw;
dp[i][j][1][0]+=(dp[i-1][k][1][0]+dp[i-1][k][1][1])%rw;
dp[i][j][1][0]%=rw;
}
}
else
{
if(j>s[i]-'0')
{
dp[i][j][1][0]+=dp[i-1][k][1][0];
dp[i][j][1][0]%=rw;
}
else if(j==s[i]-'0')
{
dp[i][j][1][0]+=dp[i-1][k][1][0];
dp[i][j][1][0]%=rw;
dp[i][j][1][1]+=dp[i-1][k][1][1];
dp[i][j][1][1]%=rw;
}
else if(j<s[i]-'0')
{
dp[i][j][1][0]+=(dp[i-1][k][1][0]+dp[i-1][k][1][1])%rw;
dp[i][j][1][0]%=rw;
}
}
}
}
}
/*for(int i=0;i<len;i++)
{
for(int j=0;j<10;j++)
{
for(int k=0;k<2;k++)
{
for(int c=0;c<2;c++)
{
//printf("%d %d %d %d %d\n",i,j,k,c,dp[i][j][k][c]);
}
}
}
}*/
int ans=0;
for(int i=0;i<10;i++)
{
for(int j=0;j<2;j++)
{
for(int k=0;k<2;k++)
{
ans+=dp[len-1][i][j][k];
ans%=rw;
}
}
}
printf("%d\n",ans);
}
T2
【问题描述】
rsw因为迟到次数太多被列入黑名单,于是被派去参加陕西妇女儿童技能大赛,大赛中共安排了m个比赛项目,算上rsw在内,共有n位选手报名参加本次比赛。(如rsw,zrx,kh,ljm,cky,大耳朵图图,大头儿子等)
经过m场比赛,组委会发现,每个项目,有且仅有两个人实力超群。(比如穿针引线项目,rsw,ljm独领风骚,健美操项目,cky,cjy风姿绰约)。
现在,组委会要推选一些人去参加全国比赛,因为每个项目都必须有人擅长,所以推选的这些人,对于每一个项目,至少要有一个人擅长。
已知,选中每个人参加比赛是有一个费用a[i],比如选中了1,3,5三个人参加比赛,就要支付a[1]*a[3]*a[5]的费用。
现在的问题是:组委会所有可行选人方案的费用总和是多少?
【输入格式】
第一行n,m,q。
接下来1行n个数字a[1]~a[n]。
接下来m行,每行2个数字u,v,代表u和v两个人共同擅长第i个项目。
【输出格式】
一行,1个数字,表示最后的结果mod q。
【样例输入】
3 2 998244353
1 1 1
1 2
2 3
【样例输出】
5
【样例解释】
合法情况有:[1,2],[1,3],[1,2,3],[2],[2,3]五种,每种的费用都是1,所以结果是1+1+1+1+1=5。
【数据范围】
对于30%的数据,n<=20
对于70%的数据,n<=28
对于100%的数据,n<=36,q<=10^9
比赛的时候没想到更好的做法,就30暴力了。
因为2^36显然会挂,然而2^18则刚好不炸,所以考虑把图分成两半处理。
首先两边分别暴力求出各自的每种选择(01状压表示)下的代价。然后对右边做高维前缀和,这个东西我以前没听过,教练讲完感觉挺有用的。它是枚举每一位,每种状态进行累加的前缀和,以此法可以求出每个状态和他的子集的和。具体也非常好写,看代码里的标注位置。
处理完后,就可以把左边暴力枚举,因为跨两边的活动必须有一边对应的点被选择,所以根据左边没有被选中的点,我们就可以知道右边至少有要选哪些点,而因为已经算过前缀和,因此直接把对应位置的数一乘就好了。
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<vector>
#include<math.h>
using namespace std;
int n,m,rw;
int a[40];
bitset<700>p[40];
bitset<700>state;
int sd[700];
long long lsum[1<<19],rsum[1<<19];
vector<int>aa[40];
vector<int>b[40];
int main()
{
scanf("%d%d%d",&n,&m,&rw);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
int mid=n>>1;
for(int i=0,x,y;i<m;i++)
{
scanf("%d%d",&x,&y);
x--;y--;
p[x][i]=1;
p[y][i]=1;
if(x>y)swap(x,y);
if(y<mid)sd[i]=0;
else if(x>=mid)sd[i]=2;
else
{
sd[i]=1;
aa[x].emplace_back(y);
b[y].emplace_back(x);
}
}
bool flag;
for(int s=0;s<(1<<mid);s++)
{
long long num=1;
state.reset();
for(int i=0;i<mid;i++)
{
if((1<<i)&s)
{
num=(num*a[i])%rw;
state|=p[i];
}
}
flag=1;
for(int j=0;j<m;j++)
{
if(sd[j]==0&&!state[j])
{
flag=0;
break;
}
}
if(flag)
{
lsum[s]=num;
//if(s==2)printf("%d\n",num);
}
}
for(int s=0;s<(1<<(n-mid));s++)
{
long long num=1;
state.reset();
for(int i=0;i<n-mid;i++)
{
if((1<<i)&s)
{
num=(num*a[i+mid])%rw;
state|=p[i+mid];
}
}
flag=1;
for(int j=0;j<m;j++)
{
if(sd[j]==2&&!state[j])
{
flag=0;
break;
}
}
if(flag)
{
rsum[s]=num;
}
}
for(int i=0;i<(n-mid);i++) //高
{
for(int s=1;s<1<<(n-mid);s++) //维
{
if((s>>i)&1) //前
{
(rsum[s^(1<<i)]+=rsum[s])%=rw;//缀
}
} //和
}
//for(int i=1;i<1<<(n-mid);i++)cout<<rsum[i]<<' '<<i<<endl;
//cout<<rsum[0]<<' '<<rsum[1]<<' '<<rsum[2]<<' '<<rsum[3]<<endl;
long long ans=0;
for(int s=0;s<(1<<mid);s++)
{
if(!lsum[s])continue;
int num=0;
//if(s==1)printf("%d\n",1);
for(int j=0;j<mid;j++)
{
if((s>>j)&1)continue;
for(int k=0;k<aa[j].size();k++)
{
num|=1<<aa[j][k]-mid;
}
}
//cout<<s<<' '<<num<<endl;
ans=(ans+(lsum[s]*rsum[num])%rw)%rw;
}
printf("%lld",ans%rw);
}
T3
【问题描述】
前一天的冒泡排序对rsw来说太简单了,所以又有了冒泡排序2,给定n,k,q,问:有多少个不同的1~n的排列,能够使得,冒泡排序k趟后,得到一个几乎正确的序列。
一个几乎正确的序列指的是:它的最长上升子序列的长度至少是n-1。
【输入格式】
一行,n,k,q
【输出格式】
一个数字,表示答案mod q。
【样例输入】
5 2 998244353
【样例输出】
114
【数据范围】
对于30%的数据,n<=10
对于100%的数据,n,k<=50,q<=10^9
这个乍一看非常毒瘤的题我这种弱渣看了压根没思路。
要做这题,我们要先来点结论:
1.k次排序过后如果一个数在离它原本位置右侧k个以内的位置上就能换回来。
2.一个序列将要排好序的时候一定是某一段序列向一个方向顺次移动一位。(相当于有一个数被插入在另两个数之间)
这样,我们就可以先分析一个将要排好序的情况。
如图,假设k=3,1可以放在左边前四个位置上的任一个,2,4,5,6同理,因为一定能换回来。而三就特殊了,如果要排k次还在图中的位置,那么它只能放在图中9
的位置,这样才能换到目标的同时又不使比他大的数被换到右边。然后剩下的数随意分配剩下的位置,这里是3*2*1。再多观察几种情况,就能推出公式:
(k+1)^(n-k-1),有n-k-i+1个位置可以放这个区间(i为区间长度),所以乘以它,然后再乘k的阶乘。
这是把一个数放到原本位置右边的情况,而把一个数放在左边的公式也能用这种方式得到。
有一个位置注意一下,就是区间长度为2时两种交换方法实际上一样,所以不要算重了。
相比深度穿越地球的脑洞,最终代码出乎意料的短:
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<vector>
#include<math.h>
using namespace std;
int n,k,rw;
long long pw[55];
int main()
{
scanf("%d%d%d",&n,&k,&rw);
k=min(n,k);
pw[0]=1;
for(int i=1;i<=n;i++)
pw[i]=(long long)(k+1)*pw[i-1]%rw;
long long ans=0;
for(int i=2;i<=n-k;i++)
ans=(ans+(long long)(n-k+1-i)*pw[n-k+1-i])%rw;
for(int i=3;i<=n-k;i++)
ans=(ans+(long long)(n-k+1-i)*pw[n-k-1])%rw;
for(int i=1;i<=k;i++)
ans=(long long)i*ans%rw;
ans=(pw[n-k]+ans)%rw;//排好
printf("%lld\n",ans);
return 0;
}