A - Domino Disaster
思路:
碰到\(U\)输出\(D\),\(L,R\)就输出\(L,R\)
B - MEXor Mixup
思路:
做个前缀异或和,细节判断一下几种情况即可,注意求出来的答案如果\(=a\),那么不能用这个数,否则\(MEX\)就不是\(a\),给前缀和和\(b\)加个值,用这个两个数即可完成构造。
C - Carrying Conundrum
题意:
一个人再进行竖式加法的时候,进位的时候,把当前位置\(i\)的进位加到了\(i+2\)上而不是\(i+1\),数位从右往左依次增大。
这样他会得到一个错误的答案\(n\)。
现在你要做的是对于一个错误答案\(n\),给出有多少对的\((x,y),(x>0,y>0)\)满足\(x+y\)在这种错误的方法下等于\(n\)。
思路1:
既然这个人把加法的进位加到了左边第二个位置,在这种情况下,可以保证如果只考虑形如\(i,i+2,i+4\)这些位置的数,那么他的加法就是符合我们正常逻辑的加法,那么可以相到,把\(n\)每隔一位的分成两个数,这样这两个数是独立的满足加法的,此时我们找到两组\((x,y) = n_1,(z,w) = n_2\),并且把\(x,z\)在十进制,按照我们拆分的方式补回去,\(y,w\)同理,会发现此时新行成的这两个数按照错误的加法相加就是的一组合法的答案。
即进位只会影响相隔一位的数,所以相邻的数是可以独立考虑的。
那么一个数\(x\)有多少种方法能够相加\(=x\),显然是\(x+1\)种,\((n_1+1)(n_2+1)\)就是我们全部的答案,但是因为每个方案中都会涉及\(0\),那么当两个数的\(0\)凑到一起时还是\(0\),所以要减去这种情况的次数,\(-2\)。
分开考虑的思路比较巧妙。
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 1e5 + 10;
int n;
char s[100];
//既然进位影响的是后两位 那么如果分别考虑两个部分的话 他们是独立的不会相互影响的 所以只要分开考虑最后一乘即可
void solve() {
scanf("%s",s+1);
n = strlen(s+1);
ll a = 0,b = 0;
for(int i = 1;i <= n;i ++) {
if(i & 1) a = a * 10 + s[i] - '0';
else b = b * 10 + s[i] - '0';
}
printf("%lld\n",(a + 1) * (b + 1) - 2);
}
int main() {
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
思路2:
既然想计算方法数,考虑能否\(dp\)做,令\(f_{i,j,k}\)表示,能够形成数\(n\)十进制下的前\(i\)位,且从低位进位获得了\(j\),并且当前这一位\(i\)对高位产生了\(k\)的进位,注意这里的低位和高位位考虑都是相邻的。
枚举在第\(i\)位上相加的两位数\(x,y\),位置\(i+1\)(倒序)的低位进位\(fromlowbit\),高位进位\(tohighbit\),假如\(x + y + fromlowbit == s[i] - '0'\)即可转移,这里为什么是\(+fromlowbit\),别忘了这种错误的加法是考虑是相隔一位,而我们记录的是相邻
转移方程:
\(f[i][tohigh][(fromlowbit+x+y)/10] += f[i+1][fromlowbit][tohigh]\)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
int n;
char s[20];
ll f[20][2][2];
void solve() {
for(int i = 0;i <= 10;i ++) {
for(int j = 0;j < 2;j ++) {
for(int k = 0;k < 2;k ++) {
f[i][j][k] = 0;
}
}
}
scanf("%s",s+1);
n = strlen(s+1);
for(int x = 0;x < 10;x ++) {//预处理最低位
for(int y = 0;y < 10;y ++) {
if((x + y) % 10 == s[n] - '0') {
if(x + y >= 10) f[n][0][1]++;
else f[n][0][0]++;
}
}
}
for(int i = n - 1;i >= 1;i --) {//存的是i-1的进位结果
for(int x = 0;x < 10;x ++) {
for(int y = 0;y < 10;y ++) {
for(int tohigh = 0;tohigh < 2;tohigh ++) {
for(int fromlow = 0;fromlow < 2;fromlow ++) {
if(((x + y + fromlow) % 10) == (s[i] - '0')) {
f[i][tohigh][(x + y + fromlow) / 10] += f[i+1][fromlow][tohigh];
}
}
}
}
}
}
//显然前两位都不能有进位 所以答案是[1][0][0] 而没有[1][0][1]
printf("%lld\n",f[1][0][0] - 2);
}
int main() {
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
其实这个\(dp\)状态也可以写成\(f_{i,j}\),表示到第\(i\)位,且向后两位进位\(j\)的方法数
转移方程:
\(f[i][(x+y+j)/10] += f[i+2][j]\)
但是这样最后知识独立的算出了最后第一位和第二位的方案数,两个再相乘\(-2\)也即为答案,这样就类似于思路1了,也是独立考虑两个部分嘛。
思路简单就不贴代码了,上面\(dp\)少写一维变一下就好了。
D - Expression Evaluation Error
题意:
给你\(s\)和\(n\),现在让你写下\(n\)个数使其在十进制表示下的和为\(s\),并且使得这个\(n\)个数在十一进制表示下尽可能的大,对于每组\(s\)和\(n\)请输入一种满足最大值的合法方案。
思路:
\(x_{10}->y_{11}\),如果我们能\(y = x\),如果这个是可行的,那么显然就是我们能做到的最优解,例如:\(97_{10}\ ->\ 97_{11}\),为了达到这种可能,我们就要尽可能的使的最高位不会因为进位而损失,如果必须损失,让更低的位承担这个损失。
尽量保持十进制下的高位,那么就用\(10^k(k\in[1,9])\)去构造,因为还有\(n\)的限制,所以我们肯定是在尽可能的情况多去用\(10\)的幂次构造,构造的同时注意一下边界情况的判断,即\(s - 10^k \ge n - 1\),保证一定能构造出来\(n\)个数。
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
int n,s;
int ksm(int a,int b) {
int res = 1;
while(b) {
if(b&1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
void solve() {
scanf("%d%d",&s,&n);
if(n == 1) {
printf("%d\n",s);
return ;
}
int x = 9;
for(int i = 9;i >= 1;i --) {
// cout << ksm(10,i) << '\n';
while(s >= ksm(10,i) && n != 1 && s >= ksm(10,i) + n - 1) {
printf("%d ",ksm(10,i));
s -= ksm(10,i);
n--;
}
}
// cout << s << ' ' << n << "***\n";
for(int i = 1;i < n;i ++) {
printf("%d ",s / n);
}
printf("%d\n",s - (s / n) * (n - 1));
}
int main() {
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
E - Non-Decreasing Dilemma
题意:
长度为\(n\)的序列\(a_{1...n}\),\(q\)次询问,每次询问两种操作:
\(1.\ 1\ x\ y\),令\(a_x = y\)。
\(2.\ 2\ l\ r\),询问区间\([l,r]\)内的连续不下降子序列有多少个,即\(l\le p\le q\le r\),且\(a_p \le a_{l+1} \le ... a_{q}\),有多少个\(p,q\)满足这个条件。
思路:
考虑两个区间合并的时候,如果左区间的右端点\(\le\)右区间的左端点,代表该两个区间可以合并,考虑合并产生的贡献,即为终点为左区间右端点的不下降序列长度 \(x\) 起点为右区间左端点的不下降序列的长度。
我们在线段树的每个节点上维护,当前区间内的总答案树,以当前区间为左端点为起点的最长连续不下降序列,当前区间右端点为终点的最长连续不下降子序列,区间合并维护答案即可。
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define eb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
#define CLOSE std::ios::sync_with_stdio(false)
#define sz(x) (int)(x).size()
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int N = 2e5 + 10;
ll sum[N<<2],lm[N<<2],rm[N<<2];
int n,q,a[N];
//类似于最长不下降子序列的方法 只需要考虑在合并时对答案的贡献即可。
//假如lson和rson合并时 如果a_mid <= a_{mid+1} 那么左侧的右连续区间和右侧的做连续区间
//会产生左侧长度和右侧长度的乘积的贡献
void pushup(int rt,int l,int r) {
int mid = (l + r) >> 1;
sum[rt] = sum[lson] + sum[rson];
if(a[mid] <= a[mid+1]) {
sum[rt] += rm[lson] * lm[rson];
}
lm[rt] = lm[lson],rm[rt] = rm[rson];
if(lm[lson] == mid - l + 1 && a[mid] <= a[mid+1]) {
lm[rt] += lm[rson];
}
if(rm[rson] == r - mid && a[mid] <= a[mid+1]) {
rm[rt] += rm[lson];
}
}
void build(int rt,int l,int r) {
if(l == r) {
sum[rt] = lm[rt] = rm[rt] = 1; return ;
}
int mid = (l + r) >> 1;
build(lson,l,mid); build(rson,mid+1,r);
pushup(rt,l,r);
}
void modify(int rt,int l,int r,int p,int v) {
if(l == r) {
sum[rt] = lm[rt] = rm[rt] = 1;
a[l] = v; return ;
}
int mid = (l + r) >> 1;
if(p <= mid) modify(lson,l,mid,p,v);
else modify(rson,mid+1,r,p,v);
pushup(rt,l,r);
}
ll query(int rt,int l,int r,int L,int R) {
if(l >= L && r <= R) return sum[rt];
int mid = (l + r) >> 1;ll ans = 0;
if(R <= mid) ans += query(lson,l,mid,L,R);
else if(L > mid) ans += query(rson,mid+1,r,L,R);
else {
//区间合并的问题 查询时别忘了 重新考虑区间合并是产生的额外贡献的操作
ans += query(lson,l,mid,L,R) + query(rson,mid+1,r,L,R);
if(a[mid] <= a[mid+1]) {
ll llen = min(rm[lson],1ll*(mid-L+1)),rlen = min(lm[rson],1ll*(R-mid));
ans += llen * rlen;
}
}
return ans;
}
void solve() {
scanf("%d%d",&n,&q);
for(int i = 1;i <= n;i ++) {
scanf("%d",&a[i]);
}
build(1,1,n);
int op,l,r;
while(q--) {
scanf("%d%d%d",&op,&l,&r);
if(op == 1) {
// a[l] = r;
modify(1,1,n,l,r);
}
else if(op == 2) {
ll ans = query(1,1,n,l,r);
printf("%lld\n",ans);
}
}
}
int main() {
int T = 1;//scanf("%d",&T);
while(T--) solve();
return 0;
}