其他分享
首页 > 其他分享> > cf1697 D. Guess The String

cf1697 D. Guess The String

作者:互联网

题意:

交互题。

猜一个小写字符串 \(s\),两种询问方式:

$|s|\le 1000, $ 两种询问的次数限制分别为 \(26,6000\)

思路:

第一种询问不能超过 26 次,那肯定是先确定每种字符的分布(但不知道具体是什么字符),再询问具体是啥字符

首先可以用 \(n\) 次第二种询问得到每种字符第一次出现的位置。方法是询问 2 1 2, 2 1 3, 2 1 4 直到回答即不同字符数增大1,说明找到新段的起点。

岔路:对某 \(s_i\) 如何找到它右边最近的相同字符,即最小的 \(k>i, \text{ s.t. }s_i=s_k\) ?

发现对于两种询问 2 i j2 i+1 j-1 ,若 \(j<k\) 则前者的回答比后者大2,若 \(j=k\) 则大1,若 \(j>k\) 则相同。这样可以二分,用 \(2log_2 n\) 次确定一段,确定所有段就要 \(n*2log_2n\) 次,太多了

正确的复杂度很可能形如 \(n*()\),这个 \(n\) 应该是去不掉的。那就需要把每次找相同字符的询问次数优化到 \((6000-n)/n=5\) 次左右,而 \(\lceil log_2 26\rceil\) 刚好就是 \(5\) !

设 \(s_i\) 待确定,它左边的所有位置都已确定。把 \(s_i\) 左边出现过的每种字符 \(ch\) 按最后出现位置 \(las(ch)\) 排序。然后询问 2 las(ch) i ,若回答为 \([las(ch),i-1]\) 中的种类数 \(cnt_{ch}\),就说明 \(s_i\) 就是 \([las(ch),i-1]\) 中的某种字符;若回答是 \(cnt+1\),就说明 \(s_i\) 不在其中,得往 \(las\) 值更小的字符找。

这个 $cnt_{ch} $ 就是排序后 \(las\) 数组中 \(ch\) 到末尾的元素数

注意上面搞了这么多我们都不知道每段具体是什么字符,用一下第一种询问就好了。实际写码不必先问完第2种最后才问所有第1种,怎么方便怎么写

良心 cf 交互题的询问次数限制总是贴着正确解法的复杂度

char ask1(int i) {
    cout << "? 1 " << i << endl;
    char c; cin >> c; return c;
}
int ask2(int l, int r) {
    cout << "? 2 " << l << ' ' << r << endl;
    int v; cin >> v; return v;
}

int n; string ans;
vector<pair<int,char>> las; //{最后出现位置,字符}

int findSame(int i) {
    sort(all(las)); int len = las.size();
    int l = 0, r = len - 1; //在las里二分
    while(l < r) {
        int mid = l + r + 1 >> 1;
        if(ask2(las[mid].fi, i) == len-mid) l = mid;
        else r = mid - 1;
    }
    return l;
}
void sol() {
    cin >> n;

    ans.pb(ask1(1)), las.pb({1,ans[0]}); //先弄一下第一个

    for(int i = 2; i <= n; i++)
        if(ask2(1, i) > las.size()) //全新的字符
            ans.pb(ask1(i)), las.pb({i, ans.back()});
        else {
            int j = findSame(i); //s[j]=s[i]
            ans.pb(las[j].se), las[j].fi = i;
        }

    cout << "! " << ans << endl;
}

标签:字符,Guess,ch,String,int,询问,cf1697,ans,las
来源: https://www.cnblogs.com/wushansinger/p/16483888.html