2020牛客暑期多校训练营(第二场)A F G J H

A. All with Pairs

题意:

给你n个字符串, f ( s , t ) f(s,t) f(s,t)代表最大的 i i i满足s0i等于tlen-i+1len。要你求出 ∑ i = 1 n \sum_{i=1}^n i=1n ∑ j = 1 n \sum_{j=1}^n j=1n f ( s i , s j ) f(si,sj) f(si,sj) 2 %(998242353)

题解:

先预处理每个字符串的后缀并把它保存到map中,然后,遍历每个字符串,从后往前遍历(因为题目让我们求最大的i),在map中找这个串,更新答案, a n s + = m p [ h a s h S i ] ∗ i ∗ i ans+=mp[hashSi]*i*i ans+=mp[hashSi]ii然后减去next【i+1】匹配的答案,既 a n s − = m p [ h a s h S i ] ∗ ( n e x t ( I + 1 ) ) ∗ ( n e x t ( I + 1 ) ) ans-=mp[hashSi]*(next(I+1))*(next(I+1)) ans=mp[hashSi](next(I+1))(next(I+1)),为什么呢,假设我们遍历到 i i i,那么 s i si si,在map中又xx个,这xx个字符串与 s i si si完全相等,那么i后面一位失配时,会移动到 n x t [ i + 1 ] nxt[i+1] nxt[i+1]位置继续匹配,但是此时匹配的数是多余的,因为题目要求最长的i。hash可以利用ull自动溢出取模,好像单hash不会冲突。

#include 
#define pi pair
#define mk make_pair
#define ull unsigned long long
#define ll long long
using namespace std;

const ull base = 101;
const int maxn = 1e6+10;
string a[maxn];
int id(char x) {
    return x-'a'+1;
}
map<ull,int>mp;
ull h[maxn];
int nxt[maxn];
const int mod = 998244353;
void getNext(int tmp)
{
    int k = -1,j = 0;
    nxt[0] = -1;
    while(j < a[tmp].size())
    {
        if(k == -1 || a[tmp][j] == a[tmp][k])
        {
            ++k,++j;
            nxt[j] = k;
        }
        else k = nxt[k];
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin >> a[i];
        ull hash = 0,bs = 1;
        for(int j=a[i].size()-1;j>=0;j--)
        {
            int tmp = id(a[i][j]);
            hash = tmp*bs + hash;
            bs *= base;
            mp[hash]++;
        }
    }
    ll ans = 0;
    for(int i=1;i<=n;i++)
    {
        h[0] = 0;
        for(int j=0;j<a[i].size();j++)
        {
            int tmp = id(a[i][j]);
            h[j+1] = h[j]*base + tmp;
        }
        getNext(i);
        for(int j=a[i].size();j>0;j--)
        {
            int xx = mp[h[j]];
            ans += 1ll*xx*j%mod *j %mod;
            if(nxt[j] != -1)ans -= 1ll*xx*(nxt[j])%mod*(nxt[j])%mod;
        }
    }
    printf("%lld\n",ans%mod);
}

J Just Shuffle

题意:

给你一个长度为1-n的全排列A,根据某种规则变换了k次(k一定是一个大质数),得到了规则p,问这个全排列变换一次的答案是什么。

题解:

参考: 首先我们知道Ak(A置换k次)= B,我们就让B作为Ak次的规则,那么,我们根据这个规则可以求出A2k,A3k……。但是题目让我们求A1,我们猜想会不会存在一个x,使得Axk = A1。(A置换 t ∗ x t * x tx次等价于A置换1次)。即 k ∗ x = 1 k * x = 1 kx=1,这显然不太可能,但是我们知道置换是有循环节的,这个循环节的大小就等于环的大小,就像一个时钟,转了一圈又回到起点一样。我们不妨假设这个循环节(环)的大小为 l e n len len,那么我们可以得到:At*x%len = A1 ,进而可以得到: x ∗ k x*k xk%len==1,如果这个等式成立,则说明,我们的猜想是正确的。要想证明这个等式成立就要用到逆元的知识了。我们给这个等式变形一下:

  • x ∗ k x*k xk% l e n len len == 1
  • x ∗ k x*k xk ≡ \equiv 1%(len)
  • x ∗ k x*k xk = 1 + p ∗ l e n p*len plen
  • x ∗ k − p ∗ l e n = = 1 x*k - p*len == 1 xkplen==1
  • 我们知道当且经典 g c d ( k , p ) = = 1 gcd(k,p)==1 gcd(k,p)==1时成立,但是题目说了k为质数,所以无论如何这个等式都会成立。
  • 故猜想得证
    我们求出x,然后把根据规则B变换x次即可,我们要怎么求出x呢? t ∗ k t*k tk%len==1 ⟶ \longrightarrow t%len * k%len == 1,我们暴力枚举t%len的范围【0,len),即可。或者exgcd求出来也行。
    暴力枚举:
#include 
#define pb push_back

using namespace std;
const int maxn = 100005;
int a[maxn],vis[maxn],sum[maxn],n,k;
vector<int>ans;
void dfs(int u)
{
    if(vis[u])return ;
    vis[u] = 1;
    ans.pb(u);
    dfs(a[u]);
}

void solve()
{
    int tmp;
    int len = ans.size();
    for(int i=0;i<ans.size();i++)
    {
        if(1ll*i*k%len == 1)
        {
            tmp = i;
            break;
        }
    }
    for(int i=0;i<ans.size();i++)
    {
        int v = ans[i];
        sum[v] = ans[(i + tmp)%len];
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d",a+i);
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])
        {
            ans.clear();
            dfs(i);
            solve();
        }
    }
    for(int i=1;i<=n;i++)printf("%d ",sum[i]);
    return 0; 
}

F Fake Maxpooling

题意:

给你一个 n ∗ m n*m nm的矩阵,aij = l c m ( i , j ) lcm(i,j) lcm(i,j),让你求着个矩阵中所有大小为 k ∗ k k*k kk的矩阵最大值的和

思路:

我们可以先 n ∗ n n*n nn递推一下每个位置的 g c d gcd gcd,然后再求lcm,先预处理出每一行到第i个元素为止(包括第i个元素在内)往前k个元素的最大值,然后竖着求一遍更新一下答案。时间复杂度是o(n*m).

#include 
#define ll long long
using namespace std;
const int maxn = 5005;
int a[maxn][maxn],b[maxn][maxn];
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            if(i == 0 || j == 0)a[i][j] = max(i,j);
            else if(i >= j)a[i][j] = a[i-j][j];
            else a[i][j] = a[i][j-i];
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            a[i][j] = i*j/a[i][j];
        }
    deque<int>q;
    for(int i=1;i<=n;i++)
    {
        while(q.size())q.pop_back();
        for(int j=1;j<=m;j++)
        {
            while(q.size() && j-q.front()+1 > k)q.pop_front();
            while(q.size() && a[i][q.back()] < a[i][j])q.pop_back();
            q.push_back(j);
            if(j >= k)b[i][j] = a[i][q.front()];
        }
    }
    ll ans = 0;
    for(int j=k;j<=m;j++)
    {
        while(q.size())q.pop_back();
        for(int i=1;i<=n;i++)
        {
            while(q.size() && i-q.front()+1 > k)q.pop_front();
            while(q.size() && b[q.back()][j] < b[i][j])q.pop_back();
            q.push_back(i);
            if(i >= k)ans += b[q.front()][j];
        }
    }
    printf("%lld\n",ans);
    return 0;
}

G Greater and Greater

题意:

给你一个A串,给你一个B串,问最多能在A中找到多少个长度等于B串的子串,使得对应位置上,ai > = >= >=bi

题解:

首先考虑暴力 n ∗ m n*m nm的做法,显然这会超时。那么我们来考虑优化,利用排序+bitset来优化,怎么搞呢?
对于样例来说:如果Bi小于Aj,f[i][j]就等于0,否则就等于1。那么我们可以发现,当存在一个斜线如下图,答案数加一,这可以通过bitset的位运算操作得出答案。但是,如果直接求出这个矩阵,时间复杂度还是 n ∗ m n*m nm
2020牛客暑期多校训练营(第二场)A F G J H_第1张图片
那么我们就可以通过排序,巧妙的转换一下,我们不用矩阵来保存了,我们数组来保存信息,我们从大到小排序,那么如果Aj > Bi,那么Aj肯定也大于Bi后面那些数。所以我们枚举到Bi时f里面的信息对Bi来说肯定也是合法的(因为这些信息都大于比他大的数了,那么肯定也大于他)。这个本质跟上面那个没有什么不同,只不过通过更换枚举的顺序,降低枚举的次数而已。

时间复杂度:o( n ∗ m n*m nm / 32)

#include 

using namespace std;
const int maxn = 150005;
struct node{
    int val,id;
    bool operator < (const node & t)const{
        return val > t.val;
    }
}a[maxn],b[maxn];
bitset<maxn>ans,f;
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i].val);
        a[i].id = i;
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d",&b[i].val);
        b[i].id = i;
    }
    sort(a,a+n);
    sort(b,b+m);
    int j = 0;
    ans.set();
    for(int i=0;i<m;i++)
    {
        while(j < n && a[j].val >= b[i].val)
            f.set(a[j++].id);
        ans &= (f>>b[i].id);   
    }
    cout<<ans.count()<<'\n';
    return 0;
}

H Happy Triangle

题意:

给你一个multiset,最开始时空的,你有三种操作,操作1:往multiset里面插入一个值x。操作2:删除multiset里面的一个值x(如果有多个,只删除其中一个),操作3:查询multiset中是否存在两个值,使得这两个值与x能构成三角形。

题解:

参考 感觉这题即使有思路也不能ac,有太多细节需要处理了。构成三角型的条件有三种:

  • 任意两边之和大于第三边,任意两边只差小于第三边。
  • 存在两条边之和大于第三边,并且这两天边只差小于第三边。
    这题我们用第二个条件,map加动态开点线段树,线段树维护什么呢,维护两个相邻的数的差。
    对于操作1/2:我们需要更新map、线段树里面的信息。
    对于操作3:我们需要在map中查询x/2+1,位置的数,因为要保证两边之和大于第三边,那么至少有一个数会大于他的一半。如果这个位置的数+他前一个位置的数(最小的两边之和大于第三边),那么就是Yes,否则看这个位置的下一个位置到1e9中有没有差值小于这个数的,有就输出Yes,否则就是No。
#include 
using namespace std;

const int maxn = 2e5+10,N = 1e9+10;
int tr[maxn * 20],ls[maxn * 20],rs[maxn * 20],cnt,rt;
#define mid (l + r)/2
map<int,int>mp;

void up(int &o,int l,int r,int p,int val)
{
    if(!o)o = ++cnt;
    if(l == r)
    {
        tr[o] = val;
        return ;
    }
    if(p <= mid)up(ls[o],l,mid,p,val);
    else up(rs[o],mid+1,r,p,val);
    int ans = N;
    if(ls[o])ans = min(ans , tr[ls[o]]);
    if(rs[o])ans = min(ans , tr[rs[o]]);
    tr[o] = ans;
}
int qu(int o,int l,int r,int L,int R)
{
    if(!o || L > R)return N;
    if(l >= L && r <= R)return tr[o];
    int ans = N;
    if(L <= mid)ans = min(ans , qu(ls[o],l,mid,L,R));
    if(R > mid)ans = min(ans , qu(rs[o],mid+1,r,L,R));
    return ans;
}
void adds(int x)
{
    mp[x]++;
    if(mp[x] == 1)   //之前没有x 
    {
        auto it = mp.lower_bound(x);
        ++it;
        if(it != mp.end() && it->second == 1)up(rt,1,N,it->first,it->first-x);   //更改x的后驱:有后驱,而且只有一个数 
        --it;
		if(it != mp.begin()) //更改x :x有前驱 
        {
            --it;
            up(rt,1,N,x,x-(it->first));  
        }
        else  //x无前驱 
        {
            up(rt,1,N,x,N);
        }
    }
    else up(rt,1,N,x,0);  //之前有x 
}
void dels(int x)
{
	auto it = mp.lower_bound(x);
    mp[x]--;
    int l = -N;
    if(it != mp.begin())  //计算前驱:如果存在 
    {
        l = (--it)->first;
        ++it;
    }
    if(mp[x] == 1)up(rt,1,N,x,x-l);  
    else if(mp[x] == 0)
    {
    	++it;
        if(it != mp.end() && it->second == 1)up(rt,1,N,it->first,it->first - l);
        up(rt,1,N,x,N); 
        mp.erase(x);
    }
}

int ask(int x)
{
    auto it = mp.lower_bound(x/2+1);
    if(it == mp.end())return N;
    if(it->second > 1)return it->first;
    
    if(it != mp.begin())
    {
    	auto l = it;
        if(it->first + (--l)->first > x)return it->first;
      
    }
    if((++it)!=mp.end())return it->first;
    return N;
}
int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int op,x;
		scanf("%d%d",&op,&x);
		if(op == 1)adds(x);
		else if(op == 2)dels(x);
		else 
		{
			if(qu(rt,1,N,ask(x),N) < x)puts("Yes");
			else puts("No");
		}
	}
	return 0;
}

你可能感兴趣的:(2020牛客暑期多校训练营(第二场)A F G J H)