标签:def mis 情况 ati 元素 bool 必须 printf 受限
题目链接:https://codeforces.com/contest/1330
随便弄弄。
题意:给一串n个数字,范围在[1,n-1],求有多少种方法,把它断成前后两段排列。
题解:要断成两段排列,首先必须要同一个数字最多出现2次。在满足这个条件的情况下,某段i个数字的前缀形成一个排列的充要条件是:1、i个数字互不重复;2、最大值是i。这样搞一搞前缀和后缀,然后数一数前缀和后缀同时成立的方案。
题意:给 \(m\) 个长度线段A,第 \(i\) 条线段长度为 \(li\) ,要求把这些线段A按 \([1,m]\) 的顺序依次覆盖在线段B \([1,n]\) 上(不能左右越界),后面覆盖的线段A会把前面的线段A遮住。要求整条线段B \([1,n]\) 的每段都至少被一条线段A覆盖,且每条线段A至少露出1段(没有被其他线段A完全遮住)。
题解:一开始想了很多奇奇怪怪的构造,但是觉得很不自然。而且这个长度也没有什么升降序的规律。然后想到了一种容易想到的不可构造情形:线段A的总长度不够
\(n\) 。然后贪心的方法是:每条线段都依次露出最左的一小段,最后会覆盖住线段B的左半侧,假如还没有把线段B覆盖完,那么把最后一条线段A平移到最右侧,这样既不会对前面的线段造成更多的遮挡,也不会浪费长度,移动完之后可以把最后一条线段整条忽视掉,尝试把倒数第二条线段右移,直到把整条线段B都覆盖。那么是否还有一种不可构造情形是:每条线段A都露出1段之后,最长的线段A不能超过 \(m-1\) 呢?这个是错的,前面的线段A可以更长,例如第1条线段A可以长到 \(n\) ,第2条线段A可以长到 \(n-1\) ,以此类推。
int l[100005];
int ans[100005];
void TestCase() {
int n, m;
scanf("%d%d", &n, &m);
ll sum = 0;
bool suc = 1;
for(int i = 1; i <= m; ++i) {
scanf("%d", &l[i]);
sum += l[i];
if(l[i] + i - 1 > n)
suc = 0;
}
if(suc == 0 || sum < n) {
puts("-1");
return;
}
for(int i = 1; i <= m; ++i)
ans[i] = i;
int R = n + 1;
for(int i = m; i >= 2; --i) {
if(R - l[i] > ans[i - 1]) {
ans[i] = R - l[i];
R -= l[i];
} else
break;
}
for(int i = 1; i <= m; ++i)
printf("%d%c", ans[i], " \n"[i == m]);
return;
}
一个有明显dp特征的计数题。
题意:给出两个比较大的整数 \(d,m\) ,求满足下面现在条件的数列a的数量模 \(m\) 的值,模 \(m\) 是为了不希望大家OEIS得太爽。
1、数列a至少有一个元素
2、数列a严格升序,取值范围为 \([1,d]\)
3、设数列b为:
\(\;\;\;\;\;\; b_1=a_1\)
\(\;\;\;\;\;\; b_i=b_{i-1} \oplus a_i (i \geq 2)\)
4、数列b严格升序
题解:其实仔细算出来之后发现应该还是可以OEIS得很爽,毕竟只和 \(d\) 有关。
首先观察这个奇奇怪怪的条件,异或之后要升序?那么很显然 \(a_i\) 的最高位1不能与 \(a_{i-1}\) 的最高位1相同,否则将导致 \(b_i\) 失去这个最高位。同时由于限制2, \(a_i\) 的最高位1不能低于 \(a_{i-1}\) 的最高位1,所以 \(a_i\) 的最高位1只能高于 \(a_{i-1}\) 的最高位1,且不能导致超过 \(d\) 。
那么既然选好了这个最高位1,后面的位当然就是随便选了,所以就乘上某个2的幂次。
在忽略与 \(d\) 拥有最高位1的情况时,非常好办,记 \(d\) 的最高位1位于第 \(logd\) 位,记 \(dp[i]\) 表示“最后一个数的最高位是低位的 \(i-1\) 位的种类”,特殊的记 \(dp[0]=1\) 表示“空数列 \([]\) ”也是1种。
那么 \(dp[1]=(dp[0])*1\) ,因为最高位是1的就只有1种,就是 \([1]\) 。
那么 \(dp[2]=(dp[1]+dp[0])*2\) ,因为最高位是2的,有2种,且他们都可以跟在最高位是0的或者最高位是1的后面: \([1,2],[1,3],[2],[3]\) 。
那么 \(dp[3]=(dp[2]+dp[1]+dp[0])*4\) ,因为最高位是4的,有4种,且他们都可以跟在最高位是0的或者最高位是1或者最高位是2的后面: \([1,2,4],[2,4],[3,4],[1,2,5],[2,5],[3,5],[1,2,6],[2,6],[3,6],[1,2,7],[2,7],[3,7],[4],[5],[6],[7]\) 。
麻烦事在于找类似数位dp的时候的 \(d\) 的限制。不过仔细想想没有那么复杂,首先,最高位与 \(d\) 相同,是没有选择的余地的,直接continue。否则,若 \(d\) 的这一位是0,也是没有选择的余地的,直接continue。否则, \(d\) 的这一位是1,若这一位选择填0,那么后面的选择(若还有得选)将不受限制,这个选法也是某个2的幂次。否则, \(d\) 的这一位是1,且这一位选择填1,则继续迭代下去,直接continue。这种算法统计了在 \(d\) 的某位1填了0的种类,补上和 \(d\) 完全相等的这一种,就是 \(dp[logd]\) 的最后一个元素的选择方案,用来替代前面的那些2的幂次,还是要乘上前面的 \(dp\) 值的和。
最后,把所有的 \(dp\) 值加起来。
ll dp[80];
void TestCase() {
ll d, m;
scanf("%lld%lld", &d, &m);
ll cd = d;
int logd = 0;
while(cd) {
++logd;
cd >>= 1;
}
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 1; i <= logd; ++i) {
if(i < logd) {
//最高为取1,后面i-1位任取
ll cnt = (1ll << (i - 1)) % m;
ll prefix = 0;
for(int j = i - 1; j >= 0; --j)
prefix += dp[j];
prefix %= m;
dp[i] = prefix * (1ll << (i - 1)) % m;
} else {
//选d本身,是1种选法
ll cnt = 1;
for(int j = logd - 1; j >= 0; --j) {
//最高位必定取1
if(j == logd - 1)
continue;
//不是最高位,且d这一位是1
if((1ll << j)&d)
//这一位选择0,后面的任选
cnt += (1ll << j) % m;
//这一位依然受限
}
ll prefix = 0;
for(int j = i - 1; j >= 0; --j)
prefix += dp[j];
prefix %= m;
dp[i] = prefix * cnt % m;
}
}
ll sum = 0;
for(int i = 1; i <= logd; ++i)
sum += dp[i];
sum %= m;
printf("%lld\n", sum);
}
因为担心溢出FST重交了一发,但是事实上这个并不会溢出,虽然做了很多次加法,模之前做乘法有溢出的可能,但是未必参与加法的结果有这么大(因为这个数值几乎是只与位数有关的,而且每次扩大一个很大的倍数,非常容易与5e8之类大数的擦肩而过)。
不过反正上不了橙色,不亏。
启发:注意处理空数列的情况,以及这种求<=x的满足某种条件的数的个数,可以从数位受限去想,具体的做法是:若这一位选择了比较小的数,则后面的位就会解除<=x这个条件,只受到其他条件的制约,否则就继续往下处理,最后考虑是否要补上x的情况。
Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!
标签:def mis 情况 ati 元素 bool 必须 printf 受限
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12635707.html