Victory
【问题描述】
迟到大王rsw喜欢V这个字母,因为V代表着victory,当一个数字,从左向右数的时候,没有出现过先递减,再递增的情况,就被称作Victory数。
也就是说,这个数字可以是递增的,也可以是递减的,也可以是先递减再递增的,这个过程中可以出现相邻数字相等的情况,但是,就是不能出现过先递减再递增的情况。
问题是:给定n,问:1~n之间有多少个victory数。
【输入格式】
一行,一个数字n。
【输出格式】
一行,一个数字,表示答案mod 1 000 000 007
【样例输入】
120
【样例输出】
119
【数据范围】
对于30%的数据,1<=n<=108
对于100%的数据,1<=n<=10100
题解:
一道数位dp题,用到记忆化搜索,一看范围十的一百次方,肯定按位做,对于每一位,如果它的上一位没有卡到上界,那么这一位随意,如果上一位卡了上届,那么这一位也需要卡上界,注意先开始相等算下降的。
dp[i][j][k]表示位置为i的数字,当前位是什么,目前是持续上升还是持续下降的。
考试时用的递推,结果写太复杂导致多卡了一些情况GG。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define ll long long
#define mod 1000000007
char num[200];
ll dp[150][15][5];
ll wei[150];
int len;
ll dfs(int pos, int pre, int now, int flag)
{
if (pos > len - 1)
{
if (now == 4)
return 0;
return 1;
}
if (!flag && dp[pos][pre][now] != -1)
return dp[pos][pre][now];
ll up, sum = 0;
if (flag == 0)
up = 9;
else
up = wei[pos];
for (int i = 0; i <= up; i++)
{
if (now == 3 && pre <= i)
{
sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
}
if (now == 2)
{
if (pre >= i)
sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
else
sum = (sum + dfs(pos + 1, i, 3, flag && i == up) % mod) % mod;
}
if (now == 4)
{
if (i == 0)
sum = (sum + dfs(pos + 1, i, now, flag && i == up) % mod) % mod;
else
sum = (sum + dfs(pos + 1, i, 2, flag && i == up) % mod) % mod;
}
}
if (!flag)
dp[pos][pre][now] = sum;
return sum % mod;
}
int main()
{
memset(dp, -1, sizeof(dp));
cin >> num;
len = strlen(num);
for (int i = 0; i < len; i++)
{
wei[i] = num[i] - '0';
}
ll ans = dfs(0, 0, 4, 1) % mod;
printf("%lld\n", ans);
}
技能大赛
【问题描述】
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<=109
题解:
本体题意对一个一般图,求一个点集,使得所有的边都被覆盖,看范围发现,2的36次方过不去,而2的18次方能过,发现这又是一道折半搜索的题,对于左边集合,如果有一边的左端点没选,那么一定要选右端点,并且分为在左边集合,右边集合,还是跨两边集合的情况,对于右边,因为我们需要当前状态的总代价,于是,用到高位前缀合的办法,只需要在还是0的位置,异或成1即可,即
for (int j = 0; j < re; j++)
for (int st = 0; st < (1 << re); st++)
if (~st >> j & 1)
sum[st] = (sum[st] + sum[st | (1 << j)]) % q;
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
#define LL long long
const int maxn = 18;
int n, m, q, u, v, a[maxn << 1], sum[1 << maxn], ll[maxn], lr[maxn], rr[maxn], need;
LL now, res;
int main()
{
cin >> n >> m >> q;
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
int le = (n + 1) / 2, re = n - le;
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &u, &v);
u--, v--;
if (u > v)
swap(u, v);
if (u < le)
{
if (v < le)
ll[u] |= (1 << v);
else
lr[u] |= (1 << (v - le));
}
else
rr[u - le] |= (1 << (v - le));
}
for (int st = 0; st < (1 << re); st++)
{
LL now = 1;
for (int j = 0; j < re; j++)
{
if (st >> j & 1)
now = now % q * a[j + le] % q;
else
now *= ((rr[j] | st) == st);
}
sum[st] = now;
}
for (int j = 0; j < re; j++)
for (int st = 0; st < (1 << re); st++)
if (~st >> j & 1)
sum[st] = (sum[st] + sum[st | (1 << j)]) % q;
int res = 0;
for (int st = 0; st < (1 << le); st++)
{
LL now = 1;
int need = 0;
for (int j = 0; j < le; j++)
{
if (st >> j & 1)
now = now % q * a[j] % q;
else
now *= ((ll[j] | st) == st), need |= lr[j];
}
res = (res + now % q * sum[need] % q) % q;
}
printf("%d\n", res);
}
冒泡排序2
【问题描述】
前一天的冒泡排序对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<=109
题解:
首先我们考虑,如何使一个1~n的排列变成最长上升子序列大于等于n-1,考虑发现当把一端区间循环左移或右移一位,即能变成结果序列,例如:1 2 3 4 5 6 7 8 9 (k==3)将3 4 5 6变换后,能过形成1 2 6 3 4 5 7 8 9 或1 2 4 5 6 3 7 8 9,由冒泡排序一,我们能知道结果序列中当前第i位,是剩下的i+k中的最小值,那么我们同理,知1原来有四种,2有四种,6有四种,而3却只能放在7处,否则此位应该是3,剩下的4 5同理,最后的7 8 9,即3*2*1种方法。所以当前向→移一位总结得到,每次变换一个长度为i的序列,他的情况值即就是k的阶乘×(n-k+1-i)×(k+1)的(n-k+1-i)次方,另一种变换方式即向左移一位,即只会有一个数字被固定,即k的阶乘乘以(n-k+1-i)*(k+1)的(n-k-1)次方。
其系数n-k+1-i,即就是在长度为n-k+1的序列中,只能找到n-k+1-i种方式。
第二次枚举长度注意从3开始,因为位置为2时,向左向右移一位是一样的。
代码很短很短,思路很毒很毒:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#define ll long long
using namespace std;
ll pw[55],n,k,q;
int main()
{
cin>>n>>k>>q;
k=min(n,k);
pw[0]=1;
for(int i=1;i<=n;i++) pw[i]=(k+1)*pw[i-1]%q;
ll res=pw[n-k];
for(int i=2;i<=n-k;i++) res=(res+(n-k+1-i)*pw[n-k+1-i]%q)%q;
for(int i=3;i<=n-k;i++) res=(res+(n-k+1-i)*pw[n-k-1]%q)%q;
for(int i=1;i<=k;i++)
res=res*i%q;
printf("%lld",res);
}