2023ICPC合肥题解

文章目录

  • [F. Colorful Balloons(签到)](#F. Colorful Balloons(签到))
  • [E. Matrix Distances(思维+小结论)](#E. Matrix Distances(思维+小结论))
  • [J. Takeout Delivering(最短路)](#J. Takeout Delivering(最短路))
  • [G. Streak Manipulation(二分+dp)](#G. Streak Manipulation(二分+dp))
  • [C. Cyclic Substrings(回文自动机)](#C. Cyclic Substrings(回文自动机))

题目链接

F. Colorful Balloons(签到)

cpp 复制代码
    int n;cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i];
    map<string,int> mp;
    for(int i=1;i<=n;i++)
    {
        mp[s[i]]++;
        if(mp[s[i]]*2 > n)
        {
            cout<<s[i];
            return;
		}
	}
	cout<<"uh-oh";

E. Matrix Distances(思维+小结论)

经典的x和y可以分开算

cpp 复制代码
    int n,m;cin>>n>>m;
    vector<int> nums;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=m;j++)
     {
        cin>>c[i][j];
        nums.push_back(c[i][j]);
	 }
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            c[i][j]=lower_bound(nums.begin(),nums.end(),c[i][j])-nums.begin();
        }
    }
	int ans=0;
	vector<int> num(nums.size()+1),sum(nums.size()+1);
	for(int j=1;j<=m;j++) //
	 for(int i=1;i<=n;i++)
	 {
	     ans+=num[c[i][j]]*j - sum[c[i][j]];
	     sum[c[i][j]]+=j;
	     num[c[i][j]]++;
	 }
	for(int i=0;i<nums.size()+1;i++)num[i]=sum[i]=0;
	for(int i=1;i<=n;i++) //
	 for(int j=1;j<=m;j++)
	 {
	     ans+=num[c[i][j]]*i - sum[c[i][j]];
	     sum[c[i][j]]+=i;
	     num[c[i][j]]++;
	 }
	cout<<ans*2;

J. Takeout Delivering(最短路)

题意:

从 1 1 1走到 n n n,路径大小为最大的两条边的边权和

思路:

分别从 1 , n 1,n 1,n开始记录到每个点的最大值

枚举最大边,那么答案就是 min ⁡ ( w + max ⁡ ( d i s 1 , u , d i s v , n ) ) ( max ⁡ ( d i s 1 , u , d i s v , n ) ≤ w ) \min (w+\max(dis_{1,u},dis_{v,n}))(\max(dis_{1,u},dis_{v,n}) \leq w) min(w+max(dis1,u,disv,n))(max(dis1,u,disv,n)≤w)

cpp 复制代码
void solve(){
    int n,m;
    cin>>n>>m;
    vector<PII> adj[n+1];
    int ans=INF;
    vector<array<int,3>> e;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        if((u==1&&v==n)||(u==n&&v==1))ans=min(ans,w);
        adj[u].push_back({v,w});
        adj[v].push_back({u,w});
        e.push_back({u,v,w});
    }
    vector<int> vis(n+1);
    auto dijk=[&](vector<int> &dis,int st){
        priority_queue<PII,vector<PII>,greater<PII>> pq;
        dis[st]=0;
        vis.assign(n+1,0);
        pq.push({0,st});
        while(!pq.empty()){
            auto [d,u]=pq.top();
            pq.pop();
            if(vis[u])continue;
            vis[u]=1;
            for(auto [v,w]:adj[u]){
                int x=(dis[u]==0?w:max(dis[u],w));
                if(dis[v]>x){
                    dis[v]=x;
                    pq.push({dis[v],v});
                }
            }
        }
    };
    vector<int> dis1(n+1,INF),dis2(n+1,INF);
    dijk(dis1,1);
    dijk(dis2,n);
    for(auto [u,v,w]:e){
        if(dis1[u]<=w&&dis2[v]<=w)ans=min(ans,w+max(dis1[u],dis2[v]));
        if(dis1[v]<=w&&dis2[u]<=w)ans=min(ans,w+max(dis1[v],dis2[u]));
    }
    cout<<ans<<"\n";
}

G. Streak Manipulation(二分+dp)

给一个01字符串,问最大长度 l l l,这样的连续 1 1 1的不相连接线段至少有 k k k个,最多可以修改 m m m次 0 0 0变 1 1 1

很明显可以二分答案,并且 k k k很小可以直接dp

d p i , j dp_{i,j} dpi,j表示考虑完前 i i i个字母,构成了 j j j段长度为 l l l的线段,判断条件就是 d p n , k ≤ m dp_{n,k} \leq m dpn,k≤m

转移的时候我们枚举每个线段的合法右端点,然后获取大于等于 l l l的线段的左端点(这一部分最易出错,考虑各种边界即可,比如左右端点在原有的线段内,或者如果当前端点置1导致两个线段合并在一起不符和假设的左端点或右端点时,需要更新以下)

复杂度 O ( n log ⁡ 2 n ) O(n \log^2n) O(nlog2n),实现好一点可以 O ( n log ⁡ n ) O(n \log n) O(nlogn)

cpp 复制代码
int n,m,k;
vector<PII> seg;
int sum[N];
bool check(int x){
    vector dp(n+1,vector<int>(k+1,INF));
    dp[0][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=k;j++)dp[i][j]=dp[i-1][j];
        auto it=upper_bound(seg.begin(),seg.end(),PII{i,INF});
        if(it!=seg.end()&&it->F-1==i)continue;
        if(it!=seg.begin()){
            it--;
            if(it->F<=i&&i<=it->S&&i!=it->S)continue;
        }
        int l=i-x+1;
        if(l<1)continue;
        it=upper_bound(seg.begin(),seg.end(),PII{l,INF});
        if(it!=seg.begin()){
            it--;
            if(l<=it->S+1)l=it->F;
        }
        int pos=l==1?0:l-2;
        for(int j=1;j<=k;j++){
            if(dp[pos][j-1]==INF)continue;
            dp[i][j]=min(dp[i][j],dp[pos][j-1]+sum[i]-sum[l-1]);
        }
    }
    return dp[n][k]<=m;
}
void solve(){
    cin>>n>>m>>k;
    string s;
    cin>>s;
    s=" "+s;
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(s[i]=='0');
    for(int i=1,j;i<=n;i++){
        if(s[i]=='0')continue;
        j=i;
        while(j+1<=n&&s[j+1]==s[j])j++;
        seg.push_back({i,j});
        i=j;
    }
    int l=0,r=n;
    while(l<r){
        int mid=l+r+1>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    cout<<(l==0?-1:l);
} 

C. Cyclic Substrings(回文自动机)

设 t t t为循环本质不同回文子串, f ( t ) f(t) f(t)表示出现次数, g ( t ) g(t) g(t)表示长度,求 ∑ f ( t ) 2 g ( t ) \sum f(t)^2g(t) ∑f(t)2g(t)

回文自动机板子题,令新串为 S = S S S=SS S=SS,那么就可以求出来 S S S的回文自动机,当前的回文串是有效的当下标 i > n i \gt n i>n

然后在fail树上做一遍累加,最后统计答案

cpp 复制代码
struct PAM{
	static constexpr int ALPHABET_SIZE=10;
	struct Node{
		int len,link,cnt;// cnt表示以当前字符结尾的不同子串个数(视实际情况而定)
		array<int,ALPHABET_SIZE> next;
	};
	vector<Node> t;
	int suff;
	string s;
	PAM(){
		init();
	}
	void init(){
		t.assign(2,Node());
		t[0].len=-1;
		suff=1;
		s.clear();
	}
	int newNode(){
		t.emplace_back();
		return t.size()-1;
	}
	bool add(int c){
		int pos=s.size();
		s+=c;
		c=c-'0';
		int cur=suff,curlen=0;
		while(true){
			curlen=t[cur].len;
			if(pos-1-curlen>=0&&s[pos-1-curlen]==s[pos])break;
			cur=t[cur].link;
		}
		if(t[cur].next[c]){
			suff=t[cur].next[c];
			return false;
		}
		suff=newNode();
		t[suff].len=t[cur].len+2;
		t[cur].next[c]=suff;
		if(t[suff].len==1){
			t[suff].link=1;
			return true;
		}
		while(true){
			cur=t[cur].link;
			curlen=t[cur].len;
			if(pos-1-curlen>=0&&s[pos-1-curlen]==s[pos]){
				t[suff].link=t[cur].next[c];
				break;
			}
		}
		return true;
	}
	int next(int p,int c){
		return t[p].next[c-'0'];
	}
	int link(int p){
		return t[p].link;
	}
	int len(int p){
		return t[p].len;
	}
	int cnt(int p){
		return t[p].cnt;
	}
	int size(){
		return t.size();
	}
} pam;
void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	s+=s;
	auto &t=pam.t;
	for(int i=0;i<s.size();i++){
		pam.add(s[i]);
		if(i>n-1)t[pam.suff].cnt++;
	}
	for(int i=t.size()-1;i>=2;i--){
		t[t[i].link].cnt+=t[i].cnt;
	}
	LL ans=0;
	for(int i=2;i<t.size();i++){
		if(t[i].len<=n)ans+=1ll*t[i].cnt*t[i].cnt%mod*t[i].len%mod,ans%=mod;
	}
	cout<<ans<<"\n";
}   
相关推荐
渭雨轻尘_学习计算机ing10 分钟前
二叉树构建算法全解析
算法·程序员
C语言魔术师1 小时前
70. 爬楼梯
算法·动态规划
跳跳糖炒酸奶2 小时前
第二章、Isaaclab强化学习包装器(1)
人工智能·python·算法·ubuntu·机器人
许_安2 小时前
leetcode刷题日记——两数相加
算法·leetcode·职场和发展
夜晚中的人海2 小时前
【C语言】初阶算法相关习题(二)
c语言·开发语言·算法
PXM的算法星球2 小时前
【算法笔记】贪心算法
笔记·算法·贪心算法
傻欣3 小时前
代码随想录学习笔记---二叉树
笔记·学习·算法
WW_千谷山4_sch3 小时前
MYOJ_1349:(洛谷P3951)[NOIP 2017 提高组] 小凯的疑惑(数学公式套用,两步搞定代码)
c++·算法
我想进大厂3 小时前
图论---拓扑排序(DFS)
算法·深度优先·图论
泽02023 小时前
数据结构之排序
数据结构·算法·排序算法