杭电第五场 题解

比赛传送门
作者: fn

目录

  • 签到题
    • 1003题 VC Is All You Need 你只需要VC(风投)
    • 1006题 Cute Tree 可爱的树
  • 基本题
    • 1007题 Banzhuan 搬砖
  • 进阶题
    • 1009题 Array 数组
    • 1004题 Another String 另一个字符串
  • 高阶题
    • 1005题 Random Walk 2 随机漫步2


签到题

1003题 VC Is All You Need 你只需要VC(风投)

题目大意
k k k 维空间中,求最大的 n n n ,使得把 n n n 个点染色为黑白两种颜色时,可以被一个 k − 1 k-1 k1 维超平面分成两半,每一半颜色相同。
k ∈ [ 2 , 1 0 18 ] k∈[2,10^{18}] k[2,1018]

考察内容
猜结论
// 你只需要VC(风投) → \rightarrow 你只需要猜个结论

结论
k=1时n=2,k=2时n=3,k=3时n=4,猜测
n = k + 1 n=k+1 n=k+1

注意:(要开long long)

#include
#define ll long long
using namespace std;
ll n;
int main(){ 
	ios::sync_with_stdio(0); cin.tie(0);
	ll t; cin>>t;
	ll k;
	while(t--){
		cin>>n>>k;

		if(n<=k+1){
			cout<<"Yes"<<endl;
		}
		else{
			cout<<"No"<<endl;
		}
	}
	return 0;
}

1006题 Cute Tree 可爱的树

题目大意
给定 n n n 个元素和建树的伪代码,进行代码实现,输出树上的结点数。

考察内容
模拟

#include
#define ll long long
using namespace std;
const int N=2e5+10;
ll n,m,a[N];
ll key[N];
ll son[N<<2][3]; // 子节点 
int tot; //

void build(int id,int l,int r){
	tot++;
	// id=tot; //  
	if(l==r){return;}
	
	ll mid;
	if(r-l==1){
		mid=(l+r)/2;
		build(son[id][0]=tot,l,l);
		build(son[id][1]=tot,r,r);
		return;
	}
	
	ll b=l+ (r-l+2)/3-1; // 
	ll c=(b+r)/2;
	build(son[id][0]=tot,l,b);
	build(son[id][1]=tot,b+1,c);
	build(son[id][2]=tot,c+1,r); //
}

int main(){ 
	ios::sync_with_stdio(0); cin.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n;
		int temp; 
		for(int i=1;i<=n;i++){
			cin>>temp; // 树上保存的内容。在本题中无所谓 
		}
		
		tot=0; // 
		build(1,1,n); //
		
		cout<<tot<<endl;
	}
	return 0;
}

基本题

1007题 Banzhuan 搬砖

题目大意
n ∗ n ∗ n n*n*n nnn 的三维空间里放 1 ∗ 1 ∗ 1 1*1*1 111 的小立方体(考虑重力,放在顶端时立方体会自由下落),使得三视图都是 n ∗ n n*n nn 的正方形。在每个位置放置的代价为 x ∗ y 2 ∗ z x*y^2*z xy2z ,求最大和最小代价。

考察内容
数学知识,贪心

分析

  1. 最大代价:把每一砖块从最高处丢下。
  2. 最小代价:底面(平面 z = 1 z=1 z=1 )铺满,平面 y = 1 y=1 y=1 和平面 x = 1 x=1 x=1 除了交线和底面都铺满。

记底面代价为 X X X ,平面 y = 1 y=1 y=1 (除了交线和底面) 代价为 s 1 s1 s1 ,平面 x = 1 x=1 x=1 (除了交线和底面) 代价为 s 2 s2 s2 ,最大代价 m a x 1 max1 max1 ,最小代价 m i n 1 min1 min1 ,则有
m a x 1 = X ∗ n 2 max1=X*n^2 max1=Xn2
m i n 1 = X + s 1 + s 2 min1=X+s1+s2 min1=X+s1+s2

所有公式里的除法用乘法逆元替换即可。

求乘法逆元

#include
#define ll long long
ll exgcd(ll a,ll b,ll &x,ll &y) {
    if(!b) {
        x=1,y=0;
        return a;
    }
    ll res=exgcd(b,a%b,y,x);
    y-=a/b*x;       // x=x1,y=x1-a/b*y1   x1,y1代表下一状态
    return res;
}

int main(){
    ll a,p,x,y;  // 扩展欧几里得计算a的逆元(mod p)
    scanf("%lld%lld",&a,&p);
    ll d=exgcd(a,p,x,y);
    printf(d==1?"%lld":"-1",(x+p)%p);// 最大公约数不为1,逆元不存在,输出-1
    return 0;
}

AC代码

#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll n,m;
int main(){ 
	ios::sync_with_stdio(0); cin.tie(0);
	ll t; cin>>t;
	while(t--){
		cin>>n;
		n=n%mod; //
		
		ll ans=(n*n %mod*(n+1)%mod*(n+1)%mod*(2*n+1))%mod *83333334 %mod 
		+ ((1+n)*n%mod *500000004-1)%mod * (2+n)%mod *(n-1)%mod *500000004%mod 
		+ (n*(n+1)%mod*(2*n+1)%mod*166666668%mod -1)%mod *(2+n)%mod *(n-1)%mod*500000004%mod ; 
		
		cout<<(ans+mod)%mod<<endl;
		
		
		ans=(n*n %mod*(n+1)%mod*(n+1)%mod*(2*n+1))%mod *83333334 %mod  *n%mod*n%mod;
		cout<<(ans+mod)%mod<<endl;
	}
	return 0;
}

进阶题

1009题 Array 数组

来源
洛谷 P4062 [Code+#1]Yazid 的新生舞会

题目大意
给定一个整数数组 a [ 1.. n ] a[1..n] a[1..n]

计算有多少个连续子段 a [ L , R ] a[L,R] a[L,R] ( R ≥ L ) (R ≥ L) (RL) , 满足子段的众数出现次数严格大于其他整数的出现次数。

样例输入

1
10
3303 70463 3303 3303 3303 70463 3303 3303 70463 70463

样例输出

47

考察内容
前缀和

分析
复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

使用哈希表(stl unordered_set)计数x的出现,依次遍历每一个数,考虑合并相邻两块同一个数的影响。

结论:把每个位置离散化后求前缀和,然后去数前缀和数组的每个位置前面有几个比当前位置更小的,得到贡献数组,贡献求和就是答案。

原始数组是 1 1 2 1 ,离散化一下 1 1 -1 1 ,前缀和一下 0 1 2 1 2 ,贡献 0 1 2 1 3,输出 8 。

s u m sum sum 为当前前缀和, f 1 [ s u m ] f1[sum] f1[sum] 表示前缀和为 s u m sum sum 的点有 f 1 [ s u m ] f1[sum] f1[sum] 个,对于每个点的贡献 n o w now now 就是所有小于 s u m sum sum f 1 [ s u m ] f1[sum] f1[sum] 的和,如果接下来有一堆-1,就一下跳到这堆-1的末尾,需要做一次差分数组 f 2 f2 f2 ,在起点做-1标记,终点做+1标记,走到i时若 f 2 [ s u m ] f2[sum] f2[sum] 不为0,则用 f 2 [ s u m ] f2[sum] f2[sum] 更新 f 1 f1 f1 ,同时更新 f 2 [ s u m + 1 ] f2[sum+1] f2[sum+1]

#include
using namespace std;
typedef long long ll;
#define pb push_back
const int N=2e6+3;
vector<int>G[N];
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	int n,t;
	cin>>t;
	while(t--){
		cin>>n;
		unordered_set<int>s;
		vector<int>a(n+1);
		for(int i=1;i<=n;++i){
			cin>>a[i]; s.insert(a[i]); G[a[i]].pb(i);
		}
		ll ans=0;
		for(auto num:s){ // 枚举每个数作为众数 
			ll res=0;// 答案 
			ll sum=0;// 当前前缀和
			unordered_map<int,int>f1,f2;// 前缀和为sum的点有f1[sum]个 
			G[num].pb(n+1);
			ll k=0,minn=0;
			for(int j=1;j<=n;++j){
				if(j>G[num][k]) k++;
				if(a[j]!=num&&sum==minn){
					ll len=G[num][k]-j-1;
					f2[sum+1]--;
					f2[sum-len]++;
					j+=len;
					sum-=len+1;
				}
				else if (a[j]==num){
					f1[sum]+=f2[sum];
					f2[sum+1]+=f2[sum];
					f2[sum]=0;
					f1[sum]++;
					res+=f1[sum];
					sum++;
					ans+=res;
				}
				else{
					f1[sum]++;
					sum--;
					res-=f1[sum];
					ans+=res;
				}
				if(minn>sum)minn=sum;
			}
		}
		cout<<ans<<'\n';
		for(auto &i:s)G[i].clear();
	}
	return 0;
}

1004题 Another String 另一个字符串

题目大意
如果两个相同长度的字符串的不同的字符不超过 k k k 个,我们称这两个字符串满足k-匹配。

给定一个长度为 n n n 的字符串 S S S 和一个整数 k k k 。对于每个 i ∈ [ 1 , n − 1 ] i∈[1,n-1] i[1,n1] ,将 S S S 分成前后两个子串 A A A B B B A = S [ 1 , i ] A=S[1,i] A=S[1,i] B = S [ i + 1 , n ] B=S[i+1,n] B=S[i+1,n]
计算 A A A B B B 中有多少对非空子串满足k-匹配。

样例输入

3
4 0
jslj
6 1
abcazz
5 0
zzzzz

样例输出

1
1
1
5
9
10
8
5
4
8
8
4

考察内容
字符串匹配,双指针

分析
定义 f [ i ] [ j ] f[i][j] f[i][j] 表示字符串 S S S 中分别以 i , j i,j i,j 为左端点的两个子串满足k-匹配的最大长度。

换句话说, f [ i ] [ j ] f[i][j] f[i][j] 等于 使得 S [ i , i + L − 1 ] S[i,i+L-1] S[i,i+L1] S [ j , j + L − 1 ] S[j,j+L-1] S[j,j+L1] 满足k-匹配的最大L 。

考虑如何计算所有的 f [ i ] [ j ] f[i][j] f[i][j]

  • 我们枚举两个子串的左端点之差 d d d
    对于每一个 d d d ,利用k-匹配的单调性,通过双指针,我们可以在线性时间内计算出所有的 F [ i , i + d ] F[i,i+d] F[i,i+d]
  • 因此,我们可以用 O ( n 2 ) O(n^2) O(n2) 的时间复杂度计算出所有的 f [ i ] [ j ] f[i][j] f[i][j]

考虑如何计算串S的所有分割情况下的答案

  • 定义 t t t 分割是将 S S S 分为 S [ 1 , t − 1 ] S[1,t-1] S[1,t1] S [ t , n ] S[t,n] S[t,n]
    对于 t t t 分割,我们要统计的答案为 ∑ i = 1 t − 1 ∑ j = t n G [ i ] [ j ] \sum\limits_{i=1}^{t-1}\sum\limits_{j=t}^{n}G[i][j] i=1t1j=tnG[i][j] ,其中 G [ i ] [ j ] = m i n ( f [ i ] [ j ] , t − i ) G[i][j]=min(f[i][j],t-i) G[i][j]=min(f[i][j],ti)

  • 不妨从大到小枚举 t t t ,过程中维护G对答案的贡献

  • 注意到 G [ i ] [ j ] G[i][j] G[i][j] 的值不超过 t − i t-i ti 。因此可以对每个 i i i 维护一个 c o u n t i count_i counti 数组来对有贡献的 G [ i ] [ j ] G[i][j] G[i][j] 计数。换句话说, c o u n t i [ x ] count_i[x] counti[x] 表示 { G [ i ] [ j ] ∣ 2 ≤ j ≤ n } \{G[i][j] | 2≤j≤n\} {G[i][j]2jn} 中此时对答案有贡献且值为x的个数。

  • 同时我们对每个 i i i 再维护一个 m a x i max_i maxi 表示此时 { G [ i ] [ j ] ∣ 2 ≤ j ≤ n } \{G[i][j] | 2≤j≤n\} {G[i][j]2jn} 中对答案有贡献的最大值。对于t的每次减小,我们通过维护 c o u n t i count_i counti 数组和 m a x i max_i maxi ,来计算答案的变化。

整个算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

#include
#define ll long long
using namespace std;

const int maxn=5e3+10;
char s[maxn];
int f[maxn][maxn],cnt[maxn][maxn],pt[maxn];
ll ANS[maxn];
void calc_f(int n,int k){
    for (int st=2;st<=n;++st){
        int dif=0;
        for (int i=1,j=st,len=-1; j<=n; ++i,++j,--len){
            while (i+len+1<=n&&j+len+1<=n&&dif<=k){
                len++; 
                dif+=s[i+len]!=s[j+len];
            }
            f[i][j]=len+(dif<=k);
            dif-=s[i]!=s[j];
        }
    }
}
void solve(int n){
    for (int i=0;i<=n;++i){
        pt[i]=n/2+1;
        for (int j=0;j<=n;++j)
            cnt[i][j]=0;
    }
    ll ans=0;
    for (int j=n;j>1;--j){
        // update
        for (int i=1;i<j;++i)
            while (pt[i]>j-i){
                ans-=cnt[i][pt[i]];
                cnt[i][pt[i]-1]+=cnt[i][pt[i]];
                pt[i]--;
            }
        // add
        for (int i=1;i<j;++i){
            int len=min(f[i][j],j-i);
            cnt[i][len]++;
            ans+=len;
        }
        ANS[j]=ans;
        // delete
        while (pt[j-1]>=0){
            ans -= (ll)(cnt[j-1][pt[j-1]])*pt[j-1];
            pt[j-1]--;
        }
    }
}
int main()
{
    int T;
    cin>>T;
    while (T--){
        int n,k;
        scanf("%d%d%s",&n,&k,s+1);
        calc_f(n,k); // 计算f[i][j] 
        solve(n);
        for (int i=2;i<=n;++i)
            printf("%d\n",ANS[i]);
    }
    return 0;
}

高阶题

1005题 Random Walk 2 随机漫步2

题目大意
给定一个有 n n n 个顶点的有向完整图。

注意,该图有环,没有重边。

现在有一只企鹅要在图上随机行走。

1.假设他从节点S开始(开始时间 t = 1 t=1 t=1 )。

2.那么每次他都会从节点i走到节点j,概率为 P [ i ] [ j ] = W [ i ] [ j ] ∑ k = 1 n W [ i ] [ k ] ( ∑ k = 1 n W [ i ] [ k ] > 0 ) P[i][j]=\frac{W[i][j]} {∑\limits_{k=1}^{n}W[i][k]}(∑\limits_{k=1}^{n}W[i][k]>0) P[i][j]=k=1nW[i][k]W[i][j]k=1nW[i][k]>0

3.如果他在 t t t 时间在节点 i i i 上,并且在 t + 1 t+1 t+1 时间也在节点i上,他将永远留在节点 i i i 上。

企鹅希望你能帮助他计算出他开始在节点 i i i 上并在节点 j j j 上停止的概率A[i][j]。

输入矩阵W。
输出矩阵A,输出对998244353取模。

样例输入

2
2
1 1
1 1
2
1 1
1 0

样例输出

665496236 332748118
332748118 665496236
1 0
1 0

考察内容
概率,矩阵求逆

分析
第一步 列转移方程
第二步 写出矩阵形式
第三步 解矩阵方程,算出 A − 1 A^{-1} A1 矩阵,套矩阵求逆板子

#include
#define rep(i,a,b) for (int i=(a); i<(b); ++i)
using namespace std;
typedef long long ll;

const int _p = 998244353;
const int N=505; 

ll Pow(ll x,ll k) { // 快速幂 
    ll ret=1;
    for (; k; k>>=1,x=x*x%_p) if (k&1) ret=ret*x%_p;
    return ret;
}

struct Matrix{
    static const int N=511,P=998244353;
    int n,a[N][2*N]; bool t;
    
    void In(ll a[][::N],int n) {
        this->n=n;
        rep(i,1,n+1) rep(j,1,n+1) this->a[i][j]=a[i][j], this->a[i][n+j]=0;
        rep(i,1,n+1) this->a[i][i+n]=1;
    }
    
    void Out(ll b[][::N]) {
        rep(i,1,n+1) rep(j,1,n+1) b[i][j]=a[i][n+j];
    }
    
    bool getinv(){
        rep(i,1,n+1){
            if(a[i][i]==0){
                rep(j,i,n+1) if(a[j][i]) swap(a[i],a[j]);
                if(!a[i][i]) return 0;
            }
            int s=a[i][i];
            rep(j,1,n+n+1) a[i][j]=1ll*a[i][j]*Pow(s,P-2)%P;
            rep(j,1,n+1) {
                if(i==j) continue;
                s=1ll*a[j][i]*Pow(a[i][i],P-2)%P;
                rep(k,1,n+n+1) a[j][k]=(a[j][k]-1ll*a[i][k]*s)%P;
            }
        }
        rep(i,1,n+1) rep(j,1,n+n+1) a[i][n+j]=(a[i][n+j]+P)%P;
        return 1;
    }
    
    bool Solve(ll a[][::N],int n,ll b[][::N]) {
        In(a,n),t=getinv(),Out(b); return t;
    }
}; 

int n;
ll w[N][N],d[N],lambda[N]; 
Matrix M;

int a[N][N],b[N][N];

void solve() {
    scanf("%d",&n);
    rep(i,1,n+1) d[i]=0; // 初始化d 
        
    rep(i,1,n+1) rep(j,1,n+1) {
        scanf("%lld",&w[i][j]);
        if (i==j) {
            lambda[i]=w[i][i];
        }
        d[i]+=w[i][j],d[i]%=_p,w[i][j]=-w[i][j];
    }
    rep(i,1,n+1) w[i][i]=d[i];

    assert(M.Solve(w,n,w)); // 

    rep(i,1,n+1) rep(j,1,n+1) w[i][j]*=lambda[j],w[i][j]%=_p;
    
    rep(i,1,n+1) rep(j,1,n+1) printf("%lld%c",w[i][j]," \n"[j==n]);
    
    return ;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--)
        solve();
    return 0;
}

你可能感兴趣的:(多校联赛,acm竞赛,icpc,算法,c++,c算法)