nflsoi 8.2 题解
C.#15563 ab串字典序 / AT_abc202_d aab aba baa
题意
请你求出由 AAA 个 a
和 BBB 个 b
组成的长度为 A+BA+BA+B 的字符串中,按字典序排列后的第 KKK 个字符串。
1≤A,B≤301\le A,B\le 301≤A,B≤30。
思路
最多有 2602^{60}260 种字符串,肯定不能一个一个枚举了。我们考虑在 1∼A+B1\sim A+B1∼A+B 位中填入恰当的 a
和 b
使字典序恰为 KKK。
dfs 决策一位填 a
还是填 b
:我们发现填 b
会使字符串的字典序变大!
对于前面 t−1t-1t−1 位全部相同,还有 A0A_0A0 个 a
和 B0B_0B0 个 b
可以填。t∼A+Bt\sim A+Bt∼A+B 位字符串 a (A0-1)×a (B0-1)×b
和 b A0×a (B0-1)×b
这两个字符串,字典序会相差“A0−1A_0-1A0−1 个 a
和 B0B_0B0 个 b
全排的不同方案数”。
因此填 b
升高那么多字典序,这个差值可以 dp 计数算出来:设 fi,jf_{i,j}fi,j 表示 iii 个 a
和 jjj 个 b
全排的不同方案数,有转移:
fi,j←fi−1,j(i>0)fi,j←fi,j−1(j>0)\begin{matrix} f_{i,j}\leftarrow f_{i-1,j}(i>0)\\ f_{i,j}\leftarrow f_{i,j-1}(j>0) \end{matrix}fi,j←fi−1,j(i>0)fi,j←fi,j−1(j>0)
初始化 f0,0=1f_{0,0}=1f0,0=1。
如果出现 a
或者 b
用完的情况,后面全部填其他字母即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=33;
ll a,b,t;
string lh[2][N];
ll f[N][N];
string dfs(ll rest,ll tota,ll totb)
{if(tota==0)return lh[1][totb];if(totb==0)return lh[0][tota];if(rest>f[tota-1][totb])return 'b'+dfs(rest-f[tota-1][totb],tota,totb-1);return 'a'+dfs(rest,tota-1,totb);//放b可以升高字典序
}
int main()
{scanf("%lld%lld%lld",&a,&b,&t);f[0][0]=1;for(int i=0;i<=a;i++){for(int j=0;j<=b;j++){if(i)f[i][j]+=f[i-1][j];if(j)f[i][j]+=f[i][j-1];}}for(int i=1;i<=a;i++)lh[0][i]=lh[0][i-1]+'a';for(int i=1;i<=b;i++)lh[1][i]=lh[1][i-1]+'b';cout<<dfs(t,a,b);return 0;
}
D.#15528 复合函数查询 / AT_abc196_e Filters
题目传送门
感觉这题挺无脑的,是计算答案的上下界,然后搞一个加法的 tagtagtag,最后对询问的 x+tagx+tagx+tag 然后限制在上下界之间——简洁优美!
机房好多人做出来,好厉害。不过我暴力都有 85pts 的。。。
E.#4940 数对数字和 / AT_arc158_c All Pair Digit Sums
题意
对于正整数 xxx,记其各位数字之和为 f(x)f(x)f(x)。例如,f(158)=1+5+8=14f(158) = 1 + 5 + 8 = 14f(158)=1+5+8=14,f(2023)=2+0+2+3=7f(2023) = 2 + 0 + 2 + 3 = 7f(2023)=2+0+2+3=7,f(1)=1f(1) = 1f(1)=1。
给定一个正整数序列 a=(a1,a2,…,aN)a = (a_1, a_2,\ldots, a_N)a=(a1,a2,…,aN),请计算 ∑i=1N∑j=1Nf(ai+aj)\displaystyle\sum_{i=1}^N\sum_{j=1}^N f(a_i + a_j)i=1∑Nj=1∑Nf(ai+aj) 的值。
1≤N≤2×1051 \leq N \leq 2 \times 10^51≤N≤2×105,1≤ai<10151 \leq a_i < 10^{15}1≤ai<1015。
思路
感觉没法从 O(n2)O(n^2)O(n2) 暴力去优化,数字和和数字本身没什么特别的关联。于是考虑把 ai+aja_i+a_jai+aj 拆开。
根据小学奥数知识,如果两个数加法过程中,没有发生进位,结果的数字和就是两数数字和相加。否则每进一次位数字和就 −9-9−9。
比如 58+4758+4758+47,8+78+78+7 进一次位,1+5+41+5+41+5+4 进一次位;f(105)=6=f(58)+f(47)−2×9f(105)=6=f(58)+f(47)-2\times 9f(105)=6=f(58)+f(47)−2×9。
那么答案变成:
∑i=1N∑j=1Nf(ai)+f(aj)−9z(ai,aj)\sum_{i=1}^N\sum_{j=1}^N f(a_i)+f(a_j)-9z(a_i,a_j)i=1∑Nj=1∑Nf(ai)+f(aj)−9z(ai,aj)
=2n∑i=1nf(ai)−9∑i=1N∑j=1Nz(ai,aj)=2n\sum_{i=1}^n f(a_i)-9\sum_{i=1}^N\sum_{j=1}^N z(a_i,a_j)=2ni=1∑nf(ai)−9i=1∑Nj=1∑Nz(ai,aj)
其中 z(x,y)z(x,y)z(x,y) 表示 x+yx+yx+y 过程中发生进位的次数。
减号左边 O(15n)O(15n)O(15n) 内随便算,问题还是在进位次数怎么算。
- 两个 nnn 位数相加,最多发生 nnn 次进位;
- 1≤ai<10151 \leq a_i < 10^{15}1≤ai<1015。
aia_iai 再大顶天就 151515 位,我们对于所有数对我们可以分别考虑 1∼151\sim151∼15 位上是否发生进位。
如果我们对每个数按位拆开,不仅还是 O(n2)O(n^2)O(n2),而且还要考虑低位会不会进个 111 过来。
对于枚举的第 ttt 位,我们不妨记录每个数的末 ttt 位:记 bt,ib_{t,i}bt,i 表示 aia_iai 的末 ttt 位,存在 aja_jaj 和 aia_iai 相加可以在 ttt 进位,当且仅当 bt,j+bt,i≥10t+1b_{t,j}+b_{t,i}\ge 10^{t+1}bt,j+bt,i≥10t+1,即 jjj 要满足 bt,j≥10t+1−bt,ib_{t,j}\ge 10^{t+1}-b_{t,i}bt,j≥10t+1−bt,i。我们对 btb_tbt 数组排序,容易用二分 lower_bound
计算合法的 jjj 的个数。
ans=ans*2*n;
ll ws=1;
for(int t=1;t<=15;t++)
{ws*=10;//10^{t+1}sort(b[t]+1,b[t]+n+1);for(int i=1;i<=n;i++){ll minus=n-(lower_bound(b[t]+1,b[t]+n+1,ws-b[t][i])-b[t])+1;//查大于等于10^{t+1}-b[t][i]的个数minusans-=9*minus;//ai+aj过程中第t为进位次数 }
}
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll n,a[N];
ll b[17][N];
ll cal(ll x)
{ll ret=0;while(x){ret+=x%10;x/=10;}return ret;
}
int main()
{scanf("%lld",&n);ll ans=0;for(int i=1;i<=n;i++){scanf("%lld",&a[i]);ans+=cal(a[i]);ll x=a[i],ws=1;for(int t=1;t<=15;t++){ws*=10;b[t][i]=x%ws;}}ans=ans*2*n;ll ws=1;for(int t=1;t<=15;t++){ws*=10;sort(b[t]+1,b[t]+n+1);for(int i=1;i<=n;i++){ll minus=n-(lower_bound(b[t]+1,b[t]+n+1,ws-b[t][i])-b[t])+1;//查大于等于10^{t+1}-b[t][i]的个数,b[j]单调 ans-=9*minus;//ai+aj过程中第t为进位次数 }}printf("%lld",ans);return 0;
}
F.#4548 树上游戏 / 洛谷 P10641 BZOJ3252 攻略
题意
给定一个有 nnn 个结点的树,树有点权且点权为正整数。现选取 kkk 条从根结点出发到叶子结点的简单路径,求这些路径的并集上所有结点的点权之和的最大值。
1≤n≤2×1051\leq n\leq 2\times 10^51≤n≤2×105,1≤wi≤231−11\leq w_i\leq 2^{31}-11≤wi≤231−1。
思路
这就很树剖啊。
我们定义重儿子为链长更长的,然后把树给剖开成一条条链——因为走过的贡献不重复算。这样剖肯定能把树剖完。
然后我们把所有链长搞下来从大到小排序,取前 kkk 大即可。
多测记得清空。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9;
int T,n,k;
ll a[N];
vector<int>G[N];
bool nolt[N];
int big[N];
ll lng[N];
void dfs(ll u,ll fa)//长剖
{for(auto v:G[u]){if(v==fa)continue;dfs(v,u);if(lng[v]>lng[big[u]])big[u]=v;}lng[u]=lng[big[u]]+a[u];
}
int tot;
ll b[N];
bool cmp(ll x,ll y)
{return x>y;
}
void clean()
{tot=0;for(int i=1;i<=n;i++){nolt[i]=big[i]=lng[i]=0;G[i].clear(); }
}
int main()
{scanf("%d",&T);int tick=0;while(T--){tick++;clean();scanf("%d%d",&n,&k);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);for(int i=1;i<n;i++){int u,v;scanf("%d%d",&u,&v);G[u].push_back(v);G[v].push_back(u);}dfs(1,0);/* cout<<"?剖:\n";for(ll u=1;u<=n;u++)cout<<big[u]<<" "<<lng[u]<<endl;*/for(int i=1;i<=n;i++)nolt[big[i]]=1;for(int i=1;i<=n;i++)if(!nolt[i])b[++tot]=lng[i];sort(b+1,b+tot+1,cmp);ll ans=0;for(int i=1;i<=min(tot,k);i++)ans+=b[i];printf("Case #%d: %lld\n",tick,ans);clean();}return 0;
}