The 2019 ICPC Asia-East Continent Final(M、E、H)

谁都会走 提交于 2020-01-23 16:18:17

The 2019 ICPC Asia-East Continent Final

大部分学习于:The 2019 ICPC Asia-East Continent Final(部分题解)


欠了一屁股的题目要补,倒序开始做吧!
大概有10套题目 & 7套网络赛题目…(署训就…)


A
在瓜大签了个A,之后坐着数了4个小时气球。


补题

M
做的时候为什么没有想到暴力…?
从1到n获取一些基底,满足基底不是任何其他数字的幂次;以这些基底和其若干次幂组成一串元素,枚举这一串数字的选取情况,不断更新最大值即可。
容易知道一串元素至多有20个,所以可以用二进制数表示这20个元素的选取情况。在每一种选取情况中嵌套循环来搜索那些需要减去b[i]b[i]的位置并打上标记,读题有一个地方不是很对:只要有一对(i,j)(i, j)满足ik=ji ^ k = j就要减去一次b[j]b[j],最开始的理解是存在上述元素对(i,j)(i, j)就仅减去一次b[j]b[j],这也是后来补题的时候卡了很久的地方。

AC代码

E
给出一个有向简单图,顶点1到顶点n之间有若干条长度相同、互不相交的路径;每条路径具有容量,一次操作可以将某条边容量减一而另一条边容量加一,问最少多少次操作后可以达到最大流量?

自己考虑的时候先注意到了最大流量即为totalFlow/linkLengthtotalFlow / linkLength,之后打算通过某种手段最优地进行分配…不过走的有点偏 不是很会做

这道题的关键在于这种往楼梯灌水的贪心思想。我们不关心总流量最大是多少,只需要知道一定存在某种策略可以将高处盈余的流量拿到低部补充水位,据此我们就尝试将这段楼梯的最低水位尽量变高。

我们先统计totalFlow(以下简称flow). 然后我们将k条路径按照权值排序后合并成一条大路径。这是因为如果固定升高的水位高度,将那些比较靠后的楼梯填满比那些比较靠前的楼梯填满要花费更多的水(因为长度差异),因而我们应该在横向上逐个进行装水,并且可以将k条楼梯全部合并在一起考虑;在合并的过程,我们将flow减去每一条楼梯的基准线,即

flowchat -= len * 1ll * vec[cnt][0]

这样子操作完后,得到的flow就是我们拆完除了基准平面后的所有楼梯后可以自由支配的用于再造楼梯的水。之后,我们记录大楼梯每一个小台阶之间的间距,并以此做以下扩流操作:

ll ans = 0;
    for(int i = 1; i <= len - 1; i++){
        if(flow < len) break;
        
        ll temp = min(addflow[i], flow / len);
        ans += i * temp;
        flow -= len * temp;
    }

如果剩余的流量较多,可以让第i个空隙的高度被填满,那么消耗的flow为lentemplen * temp,但是其中有一部分是原本存在的楼梯,所以再造的花费只有itempi * temp;如果剩余流量不够造完整i号空隙那么高的水平面,那么水流就只能上升flow/lenflow / len的高度,ans和flow的变化与前一种情况类似,就不予赘述了。

不过为什么WA了10发呢…?
因为我傻逼没有看到int * int爆long long的情况
: )

H
本题的突破点在于,在n个数的序列中需要找出至少n/2个数构成新的等比数列,所以这些被选择的数字之间的平均间距不可能太大,下面我们就定量的进行分析计算。
我们假设已经选择了不少于n/2个数字构成了等比数列。设x1表示原序列中这些元素之间间距d<=2d<=2的间距个数,x2表示间距d>2d>2的间距个数,有:

x1+x2>=ceil(n/2)1x1 + x2 >= ceil(n/2) - 1

n1>=c1x1+c2x2>=x1+3x2n - 1>=c1*x1 + c2*x2 >=x1 + 3*x2

联立两不等式可得
n1>=x1+3(ceil(n/2)1x1)n-1 >= x1 + 3(ceil(n/2)-1-x1)

x1>=12(3ceil(n/2)n2)x1>=\frac12(3*ceil(n/2)-n-2)
所以我们只需要用map统计相邻一位、两位的公比,并对那些出现次数大于上述下限的公比进行搜索答案即可,而搜索办法为开设map倒序进行dp,即可获得最大长度。

AC代码

const int maxn = 2e5 + 10;

int n, m;
int a[maxn];

unordered_map<int, int> ma;
vector<int> q;

//传入x,返回在模m下的逆元。
ll getInv(int a){
    ll x, y;
    exgcd((ll)a, m, x, y);
    return x % m;
}

int inv[maxn];

int count(int q){
    int res = 0;
    unordered_map<ll, int> mp;
    if(q == 1){
        for(itn i = n - 1; i >= 0; i--){
            mp[a[i]]++;
            res = max(res, mp[a[i]]);
        }
        return res;
    }
    
    for(int i = n - 1; i >= 0; i--){
        mp[a[i]] = 1;
        mp[a[i]] = max(mp[a[i]], mp[a[i] * 1ll * q % m] + 1);
        res = max(res, mp[a[i]]);
    }
    return res;
}
 
//#define LOCAL
int main(){
#ifdef LOCAL
    freopen("in.txt", "r", stdin);
#endif
    int _; scanf("%d", &_);
    while(_--){
        scanf("%d %d", &n, &m);
        for(int i = 0; i < n; i++){
            scanf("%d", a + i);
            inv[i] = getInv(a[i]) % m;
        }
        for(int i = 0; i + 1 < n; i++){
            ll temp = (ll)a[i + 1] * inv[i]; temp %= m;
            if(temp < 0) temp += m;
            ma[(int)temp]++;
        }
        for(int i = 0; i + 2 < n; i++){
            ll temp = (ll)a[i + 2] * inv[i]; temp %= m;
            if(temp < 0) temp += m;
            ma[(int)temp]++;
        }
        
        int least = 3 * (n + 1) / 2 - n - 2; least /= 2;
        int ans = 0;
        for(auto i: ma) if(i.second >= least){
            ans = max(ans, count(i.first));
        }
        if(ans < (n + 1) / 2) puts("-1");
        else printf("%d\n", ans);
        ma.clear();
    }
    
    return 0;
}

随机化做法
随机枚举原序列中的一些点,以其相邻公比和隔一项公比作为待选公比,用该公比往前往后寻找新序列最长长度,不断更新答案即可。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!