「ZROI 2018」提高组十连测

版权声明:本博客文章为作者原创,转载请附原文链接! https://blog.csdn.net/hydingsy/article/details/82078835

Day1

A. 「ZYB建围墙」

20 40   pts n 10 20

  结合样例和图示手算一下。

100   pts n 10 9

  显然,如果房子数量恰好能填充满一整个正六边形,那么在这个正六边形外面围一圈肯定是最优的。
  那么我们只要考虑:在正六边形的基础上如果要增加一个房子,我们只要多使用长度为 1 的墙就可以将一条边向外扩展一层(相邻两边的长度均增加 1 ,扩展的边长度减少 1 )。
  再考虑向外扩展后一层后,能多围的房子数量。假设当前正六边形的边长为 x ,那么扩展 6 次(每次扩展不同的边),每次的增加量分别是 x 1 x x x x x + 1 (很显然,连续扩展 6 次后正好又是下一个正六边形了)。
  那么,我们只要不断尝试增加边长 x ,当 x 恰好小于 n 时(下一个边长围成的面积大于 n ),贪心选择扩展次数。当围成的面积等于 n 时需要特判!

  时间复杂度 Θ ( n )

代码

#include <cstdio>
#define quit(a) return printf("%d\n",a),0
int main() {
    int n;
    scanf("%d",&n);
    for(int i=1;;++i) {
        if(3*i*(i-1)+1==n) quit(6*i);
        if(3*(i+1)*i+1>n) {
            int now=3*i*(i-1)+1;
            int ans=6*i;
            if(now+(i-1)>=n) quit(ans+1); else now+=i-1;
            if(now+i>=n) quit(ans+2); else now+=i;
            if(now+i>=n) quit(ans+3); else now+=i;
            if(now+i>=n) quit(ans+4); else now+=i;
            if(now+i>=n) quit(ans+5); else now+=i;
            if(now+(i+1)>=n) quit(ans+6); else now+=i+1;
        }
    }
    return 0;
}

B. 「ZYB和售货机」

10   pts n 5

  枚举每个按钮按几次,判断合法性后更新答案。

100   pts n 10 5

  我们首先考虑一个问题:物品是否一定能被选完?

  对此,我们只考虑 f i i 的情况(原因会在正解中解释)。如果我们从 i f i 连一条有向边,这显然是一个由根是自环的树组成的森林。而且每棵树的边都是朝向根的方向的!
  有向边 ( x , y ) 有意义当且仅当物品 x 存在且 y 可以被选择。因为边都是朝上的,那么我们从根向子树考虑,发现对于每个节点,其儿子肯定能把它的物品取空(因为每个儿子向父亲取物品,对应的 a [ i ] 不变)。因此 每一个物品都能被取完。严格地说,每个非叶子节点的物品都能被取完

  如果取消 f i i 的限制,那么形成的有向图是若干 基环内向树(基环树且树上的边都向上)。
  现在我们只要考虑这个环。如果存在环上的一个点 x ,按下这个按钮获得的收益不如另一条连向 f x 的树边,那么我们就直接把这条环边断开。这样就化为了树的做法,统计一遍即可。
  如果不存在这样的点,那么我们就找一个环边价值和树边价值的差最小的点,强制它不能选择环边,统计答案即可。
  实际上,我们只要记录到每个节点的最大和次大的边。 DFS 过程中,默认接上最大边的值,维护最大边和次大边的差的最小值。找到环之后,我们显然要断掉环——使连向某个点的边改变(从默认的最大变成次大),此时只要减去维护的最小值即可。

  时间复杂度 Θ ( n )

代码

#include <cstdio>
#include <algorithm>

const int N=100005;
int n,idx,mn,f[N],c[N],d[N],w[N],a[N],mx[N],secmx[N],dfn[N];
long long ans;

void dfs(int x) {
    if(dfn[x]==idx) {
        ans-=mn;
        return;
    }
    if(dfn[x]) return;
    dfn[x]=idx;
    if(mx[x]) {
        ans+=1LL*w[mx[x]]*a[x];
        mn=std::min(mn,w[mx[x]]-w[secmx[x]]);
        if(mx[x]^x) dfs(mx[x]);
    }
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d%d%d%d",&f[i],&c[i],&d[i],&a[i]);
    for(int i=1;i<=n;++i) {
        w[i]=d[f[i]]-c[i];
        if(w[i]<0) continue;
        if(w[i]>w[mx[f[i]]]) {
            secmx[f[i]]=mx[f[i]];
            mx[f[i]]=i;
        } else if(w[i]>w[secmx[f[i]]]) {
            secmx[f[i]]=i;
        }
    }
    for(int i=1;i<=n;++i) if(!dfn[i]) mn=(1<<30),idx++,dfs(i);
    printf("%lld\n",ans);
    return 0;
}

C. 「ZYB玩字符串」

20 40   pts | S | 8 20

  怎么暴力怎么写 QAQ

100   pts | S | 200

  观察删除 p 的过程,虽然一个完整的 p 可能被截成若干段,但是对于夹在中间的每一段,一定是能被独立删去的。
  我们用 f [ i ] [ j ] 表示区间 [ i , j ] 是否合法。

  • 如果 l e n p | ( j i + 1 ) ,那么 f [ i ] [ j ] 表示是否能用 p 把这段区间删光。
  • 否则, f [ i ] [ j ] 表示多余的部分是否恰好为 p 的前缀。

  具体转移过程(考虑第 j 个字符):
   如果它和之后的零碎字符拼成一段,那么

f [ i ] [ j ] = f [ i ] [ j ]  or  ( f [ i ] [ j 1 ]  and  s j = p ( j i ) mod l e n + 1 )

   如果对于后面零碎部分而言, j 属于夹在中间的整块,那么
f [ i ] [ j ] = f [ i ] [ j ]  or  ( f [ i ] [ j k × l e n ]  and  f [ j k × l e n + 1 ] [ j ] )

  这样的转移过程,看似复杂度是 Θ ( | S | 4 ) 的,但是对每个 p ,先判断字符集的合法性,加入一些可行性或最优性剪枝,复杂度是很不满的。

  时间复杂度:非常不满的 Θ ( | S | 4 )

代码

#include <iostream>
#include <cstring>
#define FOR(i,a,b) for(int i=a;i<=b;++i)
using namespace std;

int T,n,f[205][205],sz[305],cnt[305];
string s,t;

bool check(int len) {
    memset(cnt,0,sizeof(cnt));
    FOR(i,1,len) ++cnt[(int)t[i]];
    FOR(i,0,300) {
        if(!sz[i]&&!cnt[i]) continue;
        if((sz[i]&&!cnt[i])||sz[i]%cnt[i]) return 0;
    }
    return 1;
}
int dfs(int len,int l,int r) {
    if(~f[l][r]) return f[l][r];
    if(l>r) return f[l][r]=1;
    for(int i=r-len;i>=l;i-=len) {
        if(dfs(len,l,i)&&dfs(len,i+1,r)) return f[l][r]=1;
    }
    if(s[r]==t[(r-l)%len+1]) return f[l][r]=dfs(len,l,r-1);
    return f[l][r]=0;
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    for(cin>>T;T;--T) {
        cin>>s;
        n=(int)s.size(); s=' '+s;
        memset(sz,0,sizeof(sz));
        FOR(i,1,n) ++sz[(int)s[i]];
        string ans=s;
        FOR(len,1,n) {
            if(n%len||len>(int)ans.size()-1) continue;
            FOR(i,1,n-len+1) {
                t=' '+s.substr(i,len);
                if(!check(len)) continue;
                memset(f,-1,sizeof(f));
                if(dfs(len,1,n)) {
                    if(t.size()<ans.size()) ans=t;
                    else if(t.size()==ans.size()) ans=min(ans,t);
                }
            }
        }
        ans.erase(ans.begin());
        cout<<ans<<'\n';
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/hydingsy/article/details/82078835