23块钱打的线上赛

题目连接: ​​​​​​赛氪 OJ

记录一下

C Cut

我们可以对数字进行一些有趣的游戏,比如,把数字全部切开又能变成多少?

你现在随意地写下了一个数字  (1≤n<10**10)n (1≤n<10**10),你现在可以任意多次从任意位置切开这个数字,随后将切开的数字加起来,得到一个结果。

你现在想知道所有可能的切法结果的和是多少。

input:

125

output:

176
  • 125
  • 1+25=261+25=26
  • 12+5=1712+5=17
  • 1+2+5=81+2+5=8

思路:数字长度不会超过10位,直接暴力破解即可。复杂度大概在

void dfs(string n, int start, ll sum, ll& result){
    if (start >= sz(n)) {result += sum; return;}
    for (int len = 1, i = start; i + len <= sz(n); ++len){
        ll t = stoll(n.substr(i, len));
        dfs(n, i + len, sum + t, result);
    }
}


int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    ll result = 0;
    dfs(s, 0, 0, result);
    cout <<result << endl;
    return 0;
}

D Differ

       作为一个居家小能手,你深知收纳之道。

       至少,同一种东西不要放在一起,因为同样的东西放在一起容易分不出来……。

       现在有 (1≤n≤1000) (1≤n≤1000) 个盒子,你现在有  (2≤k≤1000)k (2≤k≤1000) 种物品,每种物品的数量有无限个。你现在要在每个盒子里都放一个物品,同时,你不想相邻的位置上摆同样的东西。

       想请问有多少种摆放方式可以满足你的要求。

思路:k个物品,n个槽,物品不连续,很容易想到对于当前位置i,能摆放的方法数为位置i-1所有的不相等的物品的方法数。转移方程有了。然后边界条件,如果只有1个槽,那么有k种方法,边界条件也有了,直接写就行,比赛的时候是写的bottom-up dp,现在写一下up-down形式的dp.

//up-down version

int dfs(vvi& memo, int n, int k, int curpos, int kth){
    if (curpos == 1){return 1;}
    int& ans = memo[curpos][kth];
    if (ans != -1) return ans;
    ans = 0;
    for (int i = 1; i <= k; ++i)if (kth != i){
        ans += dfs(memo, n, k, curpos - 1, i);
    }
    return ans;
}


int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, k; cin >> n >> k;
    vvi memo(n + 1, vi(k + 1, -1));
    ll result = 0;
    for (int i = 1; i <= k; ++i){
        result += dfs(memo, n, k, n, i);
    }
    cout << result << endl;
    return 0;
}

//bottom-up version

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, k;
    cin >> n >> k;
    vvi f(1234, vi(k + 1, 0));
    for (int i = 0; i <= k; ++i){
        f[1][i] = 1;
    }
    for (int i = 2; i <= n; ++i){
        for (int j = 1; j <= k; ++j){
            for (int g = 1; g <= k; ++g){
                if (g == j){continue;}
                f[i][j] += f[i - 1][g];
            }
        }
    }
    int sum = 0;
    for (int i = 1; i <= k; ++i){
        sum += f[n][i];
    }
    cout << sum << endl;


    return 0;
}

5EchoN

很不错的一个题,字符串前缀匹配,数据范围是1e6。暴力破解超时,然后用了二分,时间复杂度是n * logn,奈何字符串相等判断消耗的时间太多,可能不是o1,最后还是TLE了。不甘心又写了个倍增,还是TLE,放弃了。记录一下三种实现的主代码
 

最后正解应该是KMP,然而不会写

// brute force
string s;
    cin >> s;
    int n = sz(s);
    ll result = 0;
    for (int i = 0; i < n; ++i){
        for (int len = 1; len <= n; ++len){
            if (s.substr(i, len) == s.substr(0, len)){
                result += 1;
            }
            else{
                break;
            }
        }

    }
    cout << reuslt << endl;
//binary search

    string s;
    cin >> s;
    int n = sz(s);
    ll result = 0;
    for (int i = 0; i < n; ++i){
        if (s[i] != s[0]) continue;
        int l = 1, r = n;
        while (l < r){
            int mid = (l + r + 1) >> 1;
            if (s.substr(i, mid) == s.substr(0, mid)){l = mid;}
            else r = mid - 1;
        }
        if (s.substr(i, l) == s.substr(0, l)){
            result += l;
        }
    }

    cout << result << endl;

 //倍增
    string s;
    cin >> s;
    int n = sz(s);
    ll result = 0;
    for (int i = 0; i < n; ++i){
        int m = 0, p = 1;
        while (p!= 0 && m <= n){
            if (s.substr(0, m + p) == s.substr(i, m + p)){
                m += p;
                p *= 2;
            }
            else p /= 2;
        }
        result += m >= n ? n : m;
        //cout << i << "  "<< result  << endl;
    }

    cout << result << endl;

GcdGame

很有趣的一个现象:给定一个数组,然后给定数组上的两个位置,我们可以在这两个位置上左右移动(不越界的情况下),每次移动可以×移动到的方格的数字。

问:最少移动多少次可以实现两个位置不互质

思路:对于每个位置求出质因子,然后对于每个位置i,记录一下所有可能出现的质因子的距离(移动的步数)。对于两个位置x和y,根据记录的信息, 可以很快的筛选出哪个质因子是可以让它们移动最少的步数得到的。

奈何可惜的是,在记录每个位置的质因子时,数据结构出了一些问题...而且感觉也会面临MLE的问题。数据范围是1e5。

Jargonless

给定两个字符串s和t,我们可以做的操作是从s的左边或者右边删除>=0个字符,问有多少种删除方式,在删除字符操作后,t仍为s的子串。t为s的子串:s任意位置删除0或多个字符,t==s。

思路:有点抽象的题目,暴力破解的话就是试错,两个for循环,内层是判定t是否为s的子串,数据范围是3e5,不用想必然tle。于是需要找一下规律:

不知道怎么样,我们可以发现这样一个规律:如果当前s串的起点i到j表示的字符串,可以跟t进行匹配,那么j + 1到n的字符都可以删掉,从n开始删,删到j+1,一共是n - j种删除方式。

所以对于每个起点i,只要找到最近的能匹配到t字符串的右边界r,就可以O(1)的计算出以i为起点的删除元素的方法个数..时间复杂度骤降,但是如果有极端的testdata的话,还是有可能会TLE,因为找右边界是个技术活,很有可能就直接走到底了,结果还是On²..

为了避免这种情况的发生,就需要waset some capacity. Ok,我们从起点找右边界的目的是为了匹配字符串t,也就是说,我们遍历的时候所做的无效操作,是查找到了不需要的元素,如果能认识到这一点,解决掉这个不必要的查找,就可以避免TLE问题。 如果当前我们所在的位置是i,我们要匹配的元素是c,那么我们就可以想办法来缩短这个查找时间,在O1的时间内找到字符c。

我们需要一个映射数组,对于每个位置i,记录一下在它右边所有的字符的位置,这样就可以o1的查找到下一个下标,而不需要一个一个去筛选,我们只需要在每个位置上开一大小为26的数组来记录位置即可。

于是,时间复杂度变成了n * 200(字符串t的最大长度)

当然还有继续优化的方法:

1、如果当前起点开始查找,不能匹配子串t(当前位置记录的下一个位置中,没有t的最后一个元素),直接结束程序

2、。。。。

奈何写程序的时候忘记给计数的变量开大一点了,只开到了32位int型,不够用,答案一直错。debug了好久也没找出啥原因,哎,以后计数问题统统开Ull

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int inf = 0x3f3f3f3f;
    string s, t; cin >> s >> t;
    int n = sz(s), m = sz(t);
    vvi a(n + 1, vi(26, inf));
    vi b(26, inf);
    char x = ‘a';
    for (int i = n - 1; i >= 0; --i){
        a[i] = b;   b[s[i] - x] = i;
    }
    ll result = 0;
    for (int i = 0; i < n; ++i){
        int r = i; 
        if (a[r][t[m - 1] - x] == inf) break;    //一个小的prune操作,不加也不会TLE
        bool ok = true;
        int j = 0;
        if (s[r] == t[0]) j = 1;    //如果当前起点刚好是t的第一个元素
        for (j; j < m; ++j){
            auto c = t[j];
            r = a[r][c - x];
            if (r == inf) {ok = false; break;}
        }
        if (ok == false) continue;
        result += n - r;
    }
    cout <<result << endl;
    return 0;
}

GcdGame

GcdGame代码更新,比赛已结束不能提交代码,不知道这种数据结构会不会tle或者mle.

思路:先从左往右遍历,对于每个value,分解一下质因子,并存到map里面,然后每个位置放了一个map,map的第一个值是质因子的值,第二个值是数组下标的值,数组下标从0开始。

从左往右遍历完后,每个位置都存储了它左边距离它最近的质因子的下标,然后从右往左扫描:仍然是分解质因子,然后将分解后的质因子存储到一个临时map中。分解完成后,对临时map进行遍历,并与当前的位置的质因子进行对比,如果临时map中的质因子距离小于之前已经扫描好左边的质因子的距离,那么就更新一下当前质因子的下标。

这样最后得到的一个存储了map的vector,记录了每个位置的到所有质因子的距离...然而数据最大值是1e8,也就是说,质因子的范围可能是1e4量级的,对于数据长度1e5而言,很有可能MLE

这里还有个可以优化的地方,就是分解质因子的时候记录一下,再次遇到相同的数据的时候可以直接用。

上代码

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<map<int, int>> vm(n);
    map<int, int> tmap;
    vi a(n);
    for (int i = 0; i < n; ++i){
        int x; cin >> x; a[i] = x;
        for (int j = 2; j <= x; ++j){
            if (x % j == 0){
                while (x % j == 0) x /= j;
                tmap[j] = i;
            }
        }
        if (x > 1) tmap[x] = i;
        vm[i] = tmap;
    }
    tmap.clear();
    for (int i = n - 1; i >= 0; --i){
        int x = a[i];
        for (int j = 2; j <= x; ++j){
            if (x % j == 0){
                while (x % j == 0) x /= j;
                tmap[j] = i;
            }
        }
        if (x > 1) tmap[x] = i;
        for (auto v : tmap){
            int prime = v.first;
            if(vm[i].count(prime)){
                int dis = abs(i - vm[i][prime]);
                if (dis > abs(i - v.second)){
                    vm[i][prime] = v.second;
                }
            }
        }
    }
    for (int i = 0; i < q; ++i){
        int x,  y, result = 1e8; cin >> x >> y;
        x -= 1, y -= 1;         //输入下标从1开始
        for (auto v : vm[x]){
            int prime = v.first;
            //if (vm[y].count(prime) == 0) continue;// 可以删掉,因为左右两边都扫描以后,两个位置的必定都含有了所有质因子
            result = min(result, abs(y - vm[y][prime]) + abs(x - vm[x][prime]));
        }
        cout << result << "\n";
    }
    return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值