1>>
如果说,有一个序列的数,这个序列的数字个数为n,那么通过对这个序列的数字+1,-1的操作后,最多可以有多少个相同的数字。
要嘛有n个数字是相同的,这n个数的和正好整除n,要么就是n-1个数字,排序之后,去掉最大值,或者最小值。和除以n-1就可以得到这个平均数字。
2>>n层完全k叉树有多少长度为m的路径
其实大致来讲 一个节点u必须大于i 一个节点v必须在i内部 就是这个链子长度一个在i内一个不在i内
小结论:n层k叉树,总共有(k^n-1)/k-1个节点
2叉树
1 3 7 对应公式为(2^n-1)/1
3叉树
1 4 13 对应公式为(3^n-1)/2
4叉树
1 5 21对应公式为(4^n-1)/3
...
n层k叉树,总共有(k^n-1)/k-1个节点
其实这就是在计算等比数列的总和嘛!
等比数列通项为:
a(n)=a1*q^(n-1)
S(n)=a1*(1-q^n)/(1-q)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
char ch;
ll x = 0;
bool f = true;
for (ch = getchar(); !isdigit(ch); ch = getchar())
if (ch == '-')
f ^= f;
for (; isdigit(ch); ch = getchar())
x = (x << 3) + (x << 1) + ch - 48;
return f ? x : -x;
}
template <int T>
struct ModInt {
const static int MD = T;
int x;
ModInt(ll x = 0)
: x(x % MD) {}
int get() { return x; }
ModInt operator+(const ModInt& that) const {
int x0 = x + that.x;
return ModInt(x0 < MD ? x0 : x0 - MD);
}
ModInt operator-(const ModInt& that) const {
int x0 = x - that.x;
return ModInt(x0 < MD ? x0 + MD : x0);
}
ModInt operator*(const ModInt& that) const {
return ModInt((long long)x * that.x % MD);
}
ModInt operator/(const ModInt& that) const {
return *this * that.inverse();
}
void operator+=(const ModInt& that) {
x += that.x;
if (x >= MD)
x -= MD;
}
void operator-=(const ModInt& that) {
x -= that.x;
if (x < 0)
x += MD;
}
void operator*=(const ModInt& that) { x = (long long)x * that.x % MD; }
void operator/=(const ModInt& that) { *this = *this / that; }
ModInt inverse() const {
int a = x, b = MD, u = 1, v = 0;
while (b) {
int t = a / b;
a -= t * b;
std::swap(a, b);
u -= t * v;
std::swap(u, v);
}
if (u < 0)
u += MD;
return u;
}
friend ostream& operator<<(ostream& os, ModInt x) {
os << x.get();
return os;
}
};
const int mod = 1e9 + 7;
typedef ModInt<mod> mint;
mint ksm(int n, mint m) {
mint ans = 1;
for (; n; n >>= 1, m *= m)
if (n & 1)
ans *= m;
return ans;
}
int main() {
ll n = read(), m = read(), k = read();
mint ans = 0;
for (int i = min(m,n); i >= (m + 1) / 2; i--) {
mint res = ((ksm(n, k) - 1) / (k - 1)) - ((ksm(i, k) - 1) / (k - 1));
if (i != m) {
res *= k - 1;
res *= ksm(m - i - 1, k);
}
if (i * 2 == m) {
res /= 2;
}
ans += res;
}
cout << ans << "\n";
return 0;
}
3>>牛牛的回文串:牛牛的回文串 (nowcoder.com)
题解:
就是说通过对字符串的处理,获得一个回文串。增加一个字符,删除一个字符,以及改变一个字符。最短路首先得到i到j变化的最小值情况
可以通过floyard得出着这几中变换的最小值。
删除一个字符和增加一个字符可以是等价的。
(1)可以直接删除(增加)一个字符。
(2)可以将该字符变成另外一个字符在增加或者删除。
(3)可以增加一个字符后,在改变字符
(4)一边的字符变成了j,另一边的字符增加一个在变成了j
#include<bits/stdc++.h>
using namespace std;
#define ll long long
/*
解决一个回文串可以发现,添加一个字符和删除一个字符是等价的,删除一个字符相当于在另一侧添加一个字符,所以只需要计算删除一个字符
所需要的的代价即可,删除一个字符可以用过
1. 先将该字符change成另一个字符再删除
2. 添加任意一个字符再修改成该字符相当于添加
3. 将该字符替换成另一个字符c在通过添加另一个字符
4. 将该字符替换成另一个字符c,再任意添加一个字符c1,再将c1替换成c
可以先用floyd来求出任意两个字符转换的最小代价,再去考虑上述四种情况求出cost
然后使用dp来解决问题
dp状态转移方程如下
dp[i][j]表示i~j是回文串的最小代价
从后往前和从前往后其实都一样
然后考虑dp[i+1~n]dp[j-1~n]都是已经处理好的了,
所以
if(s[i]==s[j])dp[i][j]=dp[i+1][j-1];
考虑不相等的情况
1. 可以将s[i]删除val = dp[i+1][j] + cost[s[i]-'a']
2. 可以将s[j]删除val = dp[i][j-1] + cost[s[j]-'a']
3. 可以将s[i],s[j] 替换成某个字符 k
4. 可以将s[i]替换成s[j],或者s[j]替换成s[i]
*/
const int N = 60;
ll dp[N][N];
ll cost[30],change[30][30];
ll cost_erase[30],cost_add[30];
const ll INF = 1e15+10;
char s[N];
void floyd(){
for(int k=0;k<26;k++){
for(int i=0;i<26;i++){
for(int j=0;j<26;j++){
change[i][j]=min(change[i][j],change[i][k]+change[k][j]);
}
}
}
for(int i=0;i<26;i++){
for(int j=0;j<26;j++){
cost[i]=min(cost[i],min(cost_add[i],cost_erase[i]));
cost[i]=min(cost[i],change[i][j]+min(cost_add[j],cost_erase[j]));
cost[i]=min(cost[i],cost_add[j]+change[j][i]);
for(int k=0;k<26;k++){
cost[i]=min(change[i][j]+cost_add[k]+change[k][j],cost[i]);
}
}
}
}
int main(){
scanf("%s",s+1);
int m;
scanf("%d",&m);
for(int i=0;i<26;i++){
cost_erase[i]=cost_add[i]=cost[i]=INF;
for(int j=0;j<26;j++){
change[i][j]=INF;
}
}
while(m--){
char op[20],ch[2],ch1[2];
int val;
scanf("%s",op);
if(op[0]=='e'){scanf("%s%d",ch,&val);cost_erase[ch[0]-'a']=min(cost_erase[ch[0]-'a'],1ll*val);}
if(op[0]=='a'){scanf("%s%d",ch,&val);cost_add[ch[0]-'a']=min(cost_add[ch[0]-'a'],1ll*val);}
if(op[0]=='c'){scanf("%s%s%d",ch,ch1,&val);change[ch[0]-'a'][ch1[0]-'a']=min(change[ch[0]-'a'][ch1[0]-'a'],1ll*val);}
}
floyd();
int len = strlen(s+1);
for(int i=len;i>=1;i--){
for(int j=i+1;j<=len;j++){
dp[i][j]=INF;
if(s[i]==s[j])dp[i][j]=dp[i+1][j-1];
dp[i][j]=min(dp[i][j],dp[i][j-1]+cost[s[j]-'a']);
dp[i][j]=min(dp[i][j],dp[i+1][j]+cost[s[i]-'a']);
dp[i][j]=min(dp[i][j],dp[i+1][j-1]+min(change[s[i]-'a'][s[j]-'a'],change[s[j]-'a'][s[i]-'a']));
for(int k=0;k<26;k++){
dp[i][j]=min(dp[i][j],dp[i+1][j-1]+change[s[i]-'a'][k]+change[s[j]-'a'][k]);
}
}
}
if(dp[1][len]==INF)printf("-1\n");
else printf("%lld\n",dp[1][len]);
}
4>>给你一个数字串,问你有多少个序列的数字可以被3整除
dp的思想。
首先dp[i][j],构造一个二维的dp数组,第一维的i,表示的是当前位置,j可以去枚举余数。
则该dp的数值就是到了该位置,余数为j的序列数量。
因为是序列的数量,所以就是前面位置的所有数量和,之前以及达到的数量,以及可以新添的数量。
可以轻松的得到,dp[i][j]=max(dp[i-1][j]+dp[i-1][(j-a[i]+3)%3]);
答案是对1e9+7去取模。
则首先可以对每一个数位进行预处理,得到余数。在进行之后的每一次dp动态规划。
for(int i=1;s[i];i++)
a[i]=s[i]%3;
dp[0][0]=0;
for(int i=1;i<=len;i++)
{
for(int k=0;k<=2;k++)
{
dp[i][k]=(dp[i-1][k]+dp[i-1][(k-a[i]+3)%3])%mod;
}
dp[i][a[i]]++;
}
5>>数位dp的初步了解:
数位dp就是求在一个区间【l,r】范围内,满足某种约束的数字的数量,总和,平方。一般采用的是记忆化的搜索方法。一般是通过前缀和和差分的方法来得到的结果,(1,l-1)与(1,r)的差值。
比如 0-12345.则 对于高位的数字,就有着相应的限制,比如说如果前面有了123则第四位就最高是4.通过limit的传到可以知道该位置的最高位up是多少,默认是9.pos是指当前到了第几位。
一般来讲,就是说,从高位到低位一次的进行递归处理,递归到最后一层,然后慢慢回溯到直到的结果。 如果在中途过程中遇到了之前已经标记过的数字,如果limit没有限制并且不违反要求的情况下就直接return结果就可以。
#include<bits/stdc++.h>
using namespace std;
int l,r;
int a[15],f[15][2];
int dfs(int num,int pre,int k,bool limit)
// 当前位置,上一位置的值,上一位置是不是6,前面的位置是不是上界
{
if(num==0)return 1;
//能找到最后状态,说明当前方案可行,返回1
if(!limit&&f[num][k]!=-1)return f[num][k];
//如果当前的pos位置已经之前被访问过,并且同样的之前都没有一直是上界,那么我在这个位置再往下搜是毫无意义的,因为搜索到的答案跟之前一样都是那么多种
//但是如果当前位置为6的话,那么搜索情况就又要不同了,所以需要一个k标记是否为6,6的情况需要单独考虑,因为后面可选择的个数发生变化
//所以dp[i][j]表示遍历到当前位置,当前位置的上一位是不是6的情况
//如果已经是上界,那么该位置的大小是会被限制的,就不能借用之前的情况,需要重新从0-上界搜
int up,sum=0;
if(!limit)up=9;
else up=a[num];
//如果之前选择的全部都为上界,那么这个时候的上界为a[pos],否则为是9
for(int i=0;i<=up;i++)
{
if(i==4)continue;//当前位置为4
if(i==2&&pre==6)continue;//上一位为6,并且当前位为2
//都是保证枚举合法性
sum+=dfs(num-1,i,i==6,limit&&i==a[num]);
//从高到低位,上一位数字,上一位是否为6
//上一位是否选择了上界,没有选择上界后面的limit就是0,那么当前这一位就可以选择到9,limit代表了上上位置的状态,上上位置只要选择了该位的上界,并且上一位也选择了上界,那么当前位置的limit=1,就不能选9
}
if(!limit)f[num][k]=sum;
//用来更新前面的答案的,节省前面数位有存在不为上界的时间
//因为当前面位置都为上界的时候,我的这个时候可行个数是与之前的不同的,就不能用来继承
return sum;//枚举的答案
}
int slove(int x)
{
int num=0;
memset(a,0,sizeof(a));
while(x!=0)
{
num++;
a[num]=x%10;
x/=10;
}
// cout<<num<<endl;
return dfs(num,-1,0,1);
}
int main()
{ memset(f,-1,sizeof(f));
while(scanf("%d%d",&l,&r)&&l!=0&&r!=0)
{
// cout<<l<<" "<<r<<endl;
printf("%d\n",slove(r)-slove(l-1));
}
return 0;
}