CF补题记录

CF补题记录

  • Codeforces Global Round 7
  • Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated, T-shirts + prizes!)
  • CodeCraft-20 (Div. 2)
  • Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)
  • Educational Codeforces Round 83 (Rated for Div. 2)

1代表比赛时ac,0代表后来补题。

Codeforces Global Round 7

官方题解
A.Bad Ugly Numbers(1)
想了一下发现,2和3两个元素的神奇之处
1.2只会被偶数整除
2.3不会被2eX整除
结合一下23333333…3便是答案。
(1)特判一下即可。

B.Maximums(1)
发现a[i]相当于给出的,所以这就是一道简单的构造题,维护A数组的最大值即可。

C.Permutation Partitions(1)
1.可以发现对于k段的长度是没有限制的,所以我们可以贪心得到选择的点的坐标。
2.又可以发现,枚举每每两个坐标之间的距离,即是这两个点直接可以得到的方案数,总方案数算个乘积即可。

D.Prefix-Suffix Palindrome(1)
1.思路不难想,先考虑正常的头尾相同的部分,然后可以得到相同部分的长度。
2.再考虑剩下串的最长回文前缀和最长回文后缀即可。

T神写的manachar算法:

#include 
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 6e6+5;
char s[N],str[N];
int Len[N],len;
 
void getstr() {
	int k = 0;
	str[k++] = '@';
	for (int i = 0; i < len; i++) {
		str[k++] = '#';
		str[k++] = s[i];
	}
	str[k++] = '#';
	len = k;
	str[k] = 0;
}
 
int manacher() {
	int mx = 0, id;
	int maxx = -1;
	for (int i = 1; i < len; i++) {
		if (mx > i) Len[i] = min(mx - i, Len[2 * id - i]);
		else Len[i] = 1;
		while (str[i + Len[i]] == str[i - Len[i]]&&(i+Len[i])<len&&(i-Len[i])>=0) Len[i]++;
		if (Len[i] + i > mx) {
			mx = Len[i] + i;
			id = i;
			maxx = max(maxx, Len[i]);
		}
	}
	
	return (maxx - 1);
}
char s1[N];
char s2[N];
int val[30];
int w[N];
 
int main() {
	int n;
	cin>>n;
	for(int t=1;t<=n;t++){
		scanf("%s",s1);
		int l = strlen(s1);
		for(int j=0;j<l;j++)
			s2[l-1-j] = s1[j];
		int dep = 0;
		for(int j=0;j<l/2;j++){
			if(s1[j]==s2[j]){
				dep++;
			}
			else {
				break;
			}
		}
		int ct = 0;
 
		for(int j=dep;j<l-dep;j++){
			s[ct++] = s1[j];
		}
		s[ct++]='\0';
		len = strlen(s);
		getstr();
		int mxx = manacher();
		int maxx= 0;
		int pos;
 
		for(int i=1;i<len;i++){
			if((i-Len[i])==0&&(Len[i]-1)>maxx){
				maxx = Len[i]-1;
				pos = i;
			}
			if((i+Len[i])==len&&(Len[i]-1)>maxx){
				maxx = Len[i]-1;
				pos = i;
			}
		}
		mxx = maxx; 
		for(int i=0;i<dep;i++) printf("%c",s1[i]);
		 int r = (pos + mxx)/2 - 1;
            int ll = r - mxx + 1;
 
            for(int i = ll ; i <= r ; i++)
            {
                printf("%c" , s[i]);
            }
		for(int i=dep-1;i>=0;i--) printf("%c",s1[i]);
		printf("\n");
	}
    return 0;
}

后面看题解是kmp找出的最长回文前缀,感觉很新奇,之前没见过记录一下。
1.首先要知道,next数组可以表示为j位前的子串的最大重复子串的长度
2.我们将字符串原字符串 S 处理为 S+#+S’。
3.这样从头跑到尾,我们就可以知道最长回文前缀的长度了。

题解代码:

#include 
 
using namespace std;
 
const int M = (int)(2e6 + 239);
 
int pref[M], c;
 
string solve_palindrome(const string& s)
{
    string a = s;
    reverse(a.begin(), a.end());
    a = s + "#" + a;
    c = 0;
	int i = 1;
	pref[0] = 0;
	// kmp模板1 
	while(i < (int)a.size()){
		if(a[c] == a[i] || !c) pref[++i] = ++c;
		else c = pref[c];
	}
	// kmp模板2 
//    c = 0;
//    for (int i = 1; i < (int)a.size(); i++)
//    {
//        while (c != 0 && a[c] != a[i])
//            c = pref[c - 1];
//        if (a[c] == a[i])
//            c++;
//        pref[i] = c;
//    }
    return s.substr(0, c);
}
 
void solve()
{
    string t;
    cin >> t;
    int l = 0;
    while (l < (int)t.size() - l - 1)
    {
        if (t[l] != t[(int)t.size() - l - 1])
            break;
        l++;
    }
    if (l > 0)
        cout << t.substr(0, l);
    if ((int)t.size() > 2 * l)
    {
        string s = t.substr(l, (int)t.size() - 2 * l);
        string a = solve_palindrome(s);
        reverse(s.begin(), s.end());
        string b = solve_palindrome(s);
        if ((int)a.size() < (int)b.size())
            swap(a, b);
        cout << a;
    }
    if (l > 0)
        cout << t.substr((int)t.size() - l, l);
    cout << "\n";
}
 
int main()
{
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

E.Bombs(0)
现场想了半天,感觉用树状数组或线段树之类的维护一个构造的过程,但模型始终建不起来(Orz,太菜了)。

题解说的emm有点难懂,就从个人理解来记录一下(感觉有差分的意思在里面。。。)。
1.将炸弹视作一段从[1,q[i]]的-1串,表示炸弹能影响到的位置。
2.将元素视作段从[1,pos[p[i]]]的串,这样表示,只有q[i] >= pos[a[i]]的时候,这个元素才会被炸掉,不再对答案造成影响。
3.在此基础上我们维护一棵区间更新的最值线段树,当最大值小于等于0的时候,可以视作当前答案不符合要求,将更小的元素加入进来考虑。
上面已经完成了题解的操作。。。下面是自己理解的时候的一点思考。

4.假设次大答案当前答案的右边,那么它之前所有的炸弹都无法对他造成影响,所以最大值必然大于0,所以次大答案计入贡献。
PS1:如果炸弹位置大于等于次大答案大位置,但已经用来引爆之前的当前答案,可以发现,在[1,pos[当前答案]]的范围内,会增加一次1。因此,可以理解为,被前面元素排掉的炸弹,前面的元素会产生一个大值来确保炸弹已被消耗。

5.假设次大答案当前答案的左边,那么只用看,[1,pos[c次大答案]]范围内变化即可,有炸弹多出,这一段必然 <= 0,炸弹已被消耗,这一段中必然有 >0 的值

感觉这道题目就是考的建立模型,但水平不够,现在也只能做到理解,感觉有点差分的味道。
PS2:貌似还有O(n)的写法,迟点看看能不能把O(n)的写法补上。

#include 
#define lson rt << 1,l,m
#define rson rt << 1|1,m+1,r
using namespace std;
const int N = 3e5+5;

int c[N << 2],add[N << 2],pos[N],q[N],n,x;
 
inline void pushup(int rt){
	c[rt] = max(c[rt << 1],c[rt << 1|1]);
	return ;
}

inline void pushdown(int rt){
	if(add[rt]){
		add[rt << 1] += add[rt];
		add[rt << 1|1] += add[rt];
		c[rt << 1] += add[rt];
		c[rt << 1|1] += add[rt];
	}
	add[rt] = 0;
	return ;
}
 
inline void update(int L,int R,int val,int rt = 1,int l = 1,int r = n){
	if(L <= l && r <= R){
		c[rt] += val;
		add[rt] += val;
		return ;
	}
	pushdown(rt);
	int m = (l+r) >> 1;
	if(L <= m) update(L,R,val,lson);
	if(m < R) update(L,R,val,rson);
	pushup(rt);
	return ;
}
 
int main() {
    std::ios::sync_with_stdio(0);cin.tie(0);
    
    cin >> n;
    for(int i = 1;i <= n;++i) cin >> x,pos[x] = i;
    for(int i = 1;i <= n;++i) cin >> q[i];
    
    int ans = n;
	update(1,pos[ans],1);
    for(int i = 1;i <= n;++i){
    	printf("%d%c",ans,i == n ?'\n' :' ');
    	update(1,q[i],-1);
    	while(c[1] <= 0 && ans > 1) update(1,pos[--ans],1);
	}
    return 0;
}

Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated, T-shirts + prizes!)

官方题解A到H
A.Kuroni and the Gifts(1)
因为数字数字都不相同,排序后直接输出即可

B.Kuroni and Simple Strings(1)
贪心,用双指针从两边像中间扫,删除简单括弧即可,操作k必为1

C.Kuroni and Impossible Calculation(1)
因为m只有1000,根据鸽笼原理,n>m必为0,不然暴力即可,复杂度上界为m2

D.Kuroni and the Celebration(0)
因为这是一颗树,所以:
1.我们每次选择两个度为1的点进行查询如果查询结果为两个点之一,那么查询结果必为根。
2.如果不相同就把这两个点从图中删去,继续寻找度为1的点。
3.假设一直没有找到,那最后剩下的点只有一个的时候,剩下那个点必然为根。

#include
using namespace std;
typedef long long ll;
const int N = 1e3+5;
const ll M = 10007;

vector <int> e[N]; 
bool vis[N];

inline int find(int n){
	int i;
	for(i = 1;i <= n;++i)
		if(!vis[i] && e[i].size() == 1)
	return i;
}

inline void del(int u){
	int v = e[u][0];
	for(int j = 0;j < e[v].size();++j){
		if(e[v][j] == u){
			e[v].erase(e[v].begin()+j);
			break;
		}
	}
	return ;
}

int main()
{
	int n,u,v,p,q,t,cnt;
	scanf("%d",&n);
	for(int i = 1;i < n;++i){
		vis[i] = 0;
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	vis[n] = 0;cnt = n;
	
	while(1){
		p = find(n),vis[p] = 1;
		q = find(n),vis[q] = 1;
		del(p);del(q);cnt -= 2;
		
		printf("? %d %d\n",p,q);
		cout.flush();
		cin >> t;
		if(t == p || t == q || cnt <= 1){
			printf("! %d\n",t);
			cout.flush();
			break;
		}
	}
	return 0;
}

E.Kuroni and the Score Distribution(0)
发现这是一道构造题,但现场没时间写了,我们可以枚举k找规律。
对于3有:
1 + 2 = 3
对于4有:
1 + 3 = 4
对于5有:
1 + 4 = 5
2 + 3 = 5
有此我们可以发现规律,对于任何一个数字k,有(k-1)/2种组合方式。

所以我们可以尝试这样构造数组:
对于m >= (i-1)/2 的情况,我们直接使ans[i] = ans[i-1]+1
对月m < (i-1)/2 的情况,我们考虑如何限制i的组合方式即可,把m作为长度,ans[i] = ans[i-1]+ans[i-m*2]
对于m已经为0,n还没跑完的情况呢? 考虑复制为ans[i-1]+5000,就能保证没有组成他的方式
对于m不为0,但n已经跑完的方式,发现已经不可能构造出符合要求的数组,直接输出-1
PS:大于5000皆可,但要记得数组元素不能大于1000000000,不然会wa。

#include
using namespace std;
typedef long long ll;
const int N = 5e3+5;

int a[N];

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	a[1] = 1;a[2] = 2;
	for(int i = 3;i <= n;++i){
		if(m){
			if(m >= (i-1)/2) m -= (i-1)/2,a[i] = a[i-1]+1;
			else a[i] = a[i-1]+a[i-m*2],m = 0;
		}
		else a[i] = a[i-1]+5000;
	}
	if(m) printf("-1\n");
	else for(int i = 1;i <= n;++i) printf("%d%c",a[i],i == n ?'\n' :' ');
	return 0;
}

F.Kuroni and the Punishment(0)
至少到了用取模贪心,但没想到怎么确定检测范围,此处简单转述题解做法:
1.确认答案最多为n次(思路很简单,选择2为最小质因子,那么最多只用操作n次)。
2.至少有n/2个元素的操作,要小于等于1次,因此如果设一个元素为x,那么就要考虑x,x-1,x+1的质因子。
PS:为啥只考虑质因子就可以了,设x可以被两个质数pq分解,那么变成x的操作数和变成能被p整除的操作数是一样的。
3.随机化数组,从中选择S个元素,按2中说的来操作,取出质因子,然后贪心即可。题解里表述错误率低达2-S (随机化算法牛逼)。

//标程(膜拜)
#include 
using namespace std;
 
const int N = 300005, MX = 1E6;
const long long INF = 1E12;
 
int n, ans = N;
long long a[N];
bool chk[MX];
set<long long> can;
vector<int> pr;
mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
 
void init() {
    for (int i = 2; i < MX; i++) {
        if (!chk[i]) {
            pr.push_back(i);
            for (int j = i; j < MX; j += i) {
                chk[j] = true;
            }
        }
    }
}
 
void add_prime(long long u) {
    for (int &v : pr) {
        if (u % v == 0) {
            can.insert(v);
            while (u % v == 0) {
                u /= v;
            }
        }
    }
    if (u > 1) {
        can.insert(u);
    }
}
 
int solve(long long u) {
    long long ret = 0;
    for (int i = 1; i <= n; i++) {
        long long add = (a[i] < u ? u - a[i] : min(a[i] % u, u - a[i] % u));
        ret = min((long long) n, ret + add);
    }
    return ret;
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    init();
    cin >> n;
    vector<int> per;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        per.push_back(i);
    }
    shuffle(per.begin(), per.end(), mt);
    for (int i = 0; i < 100 && i < (int)per.size(); i++) {
        int u = per[i];
        add_prime(a[u]);
        add_prime(a[u] + 1);
        if (a[u] > 1) {
            add_prime(a[u] - 1);
        }
    }
    for (long long v : can) {
        ans = min(ans, solve(v));
    }
    cout << ans;
}
//奆佬非随机化算法(因为造作最多n步,你对一个元素考虑[x-n,x+n]的范围也是可以的)
#include 
#pragma GCC optimize ("O2")
#pragma GCC optimize ("unroll-loops")
//#pragma GCC optimize("no-stack-protector,fast-math")
 
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<pii, int> piii;
typedef pair<ll, ll> pll;
#define debug(x) cerr<<#x<<'='<<(x)<
#define debugp(x) cerr<<#x<<"= {"<<(x.first)<<", "<<(x.second)<<"}"<
#define debug2(x, y) cerr<<"{"<<#x<<", "<<#y<<"} = {"<<(x)<<", "<<(y)<<"}"<
#define debugv(v) {cerr<<#v<<" : ";for (auto x:v) cerr<
#define all(x) x.begin(), x.end()
#define pb push_back
#define kill(x) return cout<
 
const ld eps=1e-7;
const int inf=1000000010;
const ll INF=10000000000000010LL;
const int mod = 1000000007;
const int MAXN = 1500010, LOG=20;
 
ll n, m, k, u, v, x, y, t, a, b, ans;
ll A[MAXN], B[MAXN];
bool sieve[MAXN];
bool mark[MAXN];
 
void upd(ll p){
	if (p<2) return ;
	if (p<MAXN && mark[p]) return ;
	if (p<MAXN) mark[p]=1;
	ll tmp=0;
	for (int i=1; i<=n; i++){
		if (A[i]<=p) tmp+=p-A[i];
		else{
			ll x=A[i]%p;
			tmp+=min(x, p-x);
		}
		if (tmp>=ans) return ;
	}
	ans=min(ans, tmp);
}
 
int main(){
	ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	//freopen("input.txt", "r", stdin);
	//freopen("output.txt", "w", stdout);
	for (int i=2; i*i<MAXN; i++) if (!sieve[i]) for (int j=i*i; j<MAXN; j+=i) sieve[j]=1;
	cin>>n;
	for (int i=1; i<=n; i++) cin>>A[i];
	ans=n;
	for (int p=2; p<8000; p++) if (!sieve[p]) upd(p);
	sort(A+1, A+n+1); // ??
	ll N=0, l=max(A[1]-ans, 2ll), r=A[1]+ans;
	for (ll i=l; i<=r; i++) B[N++]=i;
	
	for (ll p=2; p<MAXN; p++) if (!sieve[p]){
		for (ll x=(l+p-1)/p*p; x<=r; x+=p){
			if (x-l<0 || x-l>=N) continue ;
			upd(p);
			while (B[x-l]%p==0 && B[x-l]) B[x-l]/=p;
		}
	}
	for (ll i=0; i<N; i++) if (B[i]>1){
		upd(B[i]);
	}
	cout<<ans<<'\n';
	
	return 0;
}

CodeCraft-20 (Div. 2)

官方题解A到F
A.Grade Allocation(1)
直接班级成绩求和和自己成绩的上限取个最小值即可

B.String Modification(1)
假设选择长度为x,字符串总长为L
1.[x,L]范围内的字符串必然平移到最前方
2.[1,x-1]范围内的字符则往后移动,这个字符串是否发生反转要视翻转次数而定(自己看看马上就懂)
3.按照上面的思路,找切割点直接暴力即可。

C.Primitive Primes(0)
题意很简单,两个多项式求积,问其第几项的系数不能被p整除。写比赛的时候,连fft都用上了,还是没A(精度不够)。
实际上这是一道思维题,我们假设找到F函数里第一个系数不会被p整除的项为i,G函数里第一个系数不会被p整除的项为j,思考他们求和之后的ai+j项,因为i之前的系数都能被p整除,其和任意数的乘积都会被p整除,j项同理。
选出这对i和j就可以保证,ai+j项的系数里,是一对p的倍数和一个不能被p整除的数字组成的,这就保证了ai+j项的系数不能被p整除。

#include 
using namespace std;
typedef long long ll;
const int N = 2e5+5;

int main() {
	ios_base::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	ll n,m,p,t,x,y;
	bool a = 0,b = 0;
	cin >> n >> m >> p;
	for(ll i = 0;i < n;++i){
		cin >> t;
		if(t%p && !a) x = i,a = 1;
	}
	for(ll i = 0;i < m;++i){
		cin >> t;
		if(t%p && !b) y = i,b = 1;
	}
	cout << x+y << endl;
    return 0;
}

D.Nash Matrix(0)
现场也是没做出来,一直在纠结从x1,y1到x2,y2假如隔的很远,他们中间都是-1该如何处理,后面看完题解发现是自己想岔了。
1.分析从x1,y1到x2,y2的,因为这是一条有终点有起点的路径,所以这条路径上的点,其坐标必然不可能是(-1,-1),所以,从所有终点开始往外搜即可。
2.分析(-1,-1)的情况,这个当时就想到了,对于每个这种点,建立一个二元组的无限循环即可,旁边的(-1,-1)走进来也能解决问题。

#include 
using namespace std;
typedef long long ll;
const int N = 1e3+5;

int x[N][N],y[N][N];
char m[N][N],dir[] = "ULDR";
int mvx[] = {1,0,-1,0};
int mvy[] = {0,1,0,-1};

inline bool check(int x1,int y1,int x2,int y2,char d1,char d2){
	if(x[x2][y2] == -1){
		m[x1][y1] = d1;
		if(m[x2][y2] == '\0') m[x2][y2] = d2;
		return 1;
	}
	return 0;
}

void dfs(int p,int q,char d){
	if(m[p][q] != '\0') return ;
	m[p][q] = d;
	for(int i = 0;i < 4;++i){
		int tx = p+mvx[i];
		int ty = q+mvy[i];
		if(x[p][q] == x[tx][ty] && y[p][q] == y[tx][ty]) dfs(tx,ty,dir[i]);
	}
	return ;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin >> n;
	for(int i = 1;i <= n;++i)
		for(int j = 1;j <= n;++j)
			cin >> x[i][j] >> y[i][j];
	
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= n;++j){
			if(x[i][j] == -1){
				bool f = (m[i][j] == '\0');
				if(f){
					bool f2 = 0;
					for(int d = 0;d < 4;++d){
						f2 |= check(i,j,i+mvx[d],j+mvy[d],dir[(d+2)%4],dir[d%4]);
					}
					if(!f2){
						cout << "INVALID\n";
						return 0; 
					}
				}
			}
			else if(x[i][j] == i && y[i][j] == j) dfs(i,j,'X');
		}
	}
	
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= n;++j){
			if(m[i][j] == '\0'){
				cout << "INVALID\n";
				return 0;
			}
		}
	}
	
	cout << "VALID\n";
	for(int i = 1;i <= n;++i){
		for(int j = 1;j <= n;++j)
			cout << m[i][j];
		cout << "\n";
	}
	return 0;
} 

Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)

题解A到H
A.Even Subset Sum Problem(1)
看数组中是否有一个偶数或者两个奇数即可。

B.Count Subrectangles(1)
思路很简单,求全1矩阵块的数量,找其因子作为一边的长度,然后算两个数组相应达到要求的长度的乘积即可。

C.Unusual Competitions(1)
贪心,对于每个合法段直接跳过,对于每个非法段统计这个非法段的长度,算入贡献。

D.Present(0)
和atcoder一题很像,不过那个是先异或再求和,简单一点。
按题解的思路敲的代码,复杂度更优的没想到。
考虑第K位为1的情况,求和在[(1 << k),(1 << (k+1)))和[(1 << k)+(1 << (k+1)),(1 << (k+2))),范围内的和都是会提供1的,sort之后再二分搜索即可。

#include 
using namespace std;
typedef long long ll;
const int N = 4e5+5;
const int M = 25;

int a[N],t[N];

int main()
{	
	int n,ans = 0;
	scanf("%d",&n);
	for(int i = 0;i < n;++i) scanf("%d",&a[i]);
	for(int i = 0;i < M;++i){
		int K = (1 << i),MOD = K << 1;
		ll cnt = 0;
		for(int j = 0;j < n;++j) t[j] = a[j] % MOD;
		sort(t,t+n);
		
		for(int j = 0;j < n;++j){
			int up = lower_bound(t,t+n,MOD-t[j]) - t;
			int down = lower_bound(t,t+n,K-t[j]) - t;
			if(j >= down && j < up) up--;
			cnt += up-down;
		}
		for(int j = 0;j < n;++j){
			int up = lower_bound(t,t+n,MOD+K-t[j]) - t;
			if(j >= up) up++;
			cnt += n-up;
		}
		cnt /= 2;  // 题目握手定理,重复算了两次,所以要除以2
		if(cnt & 1) ans |= K;
	}
	printf("%d\n",ans);
	return 0;
} 

Educational Codeforces Round 83 (Rated for Div. 2)

A.Two Regular Polygons(1)
简单思维题,正多边形里看能不能割出一个边数更少的多边形,看边数能不能整除即可。

B.Bogosort(1)
要求j-a[j]和i-a[i]不能相等,想法很简单,保证a[i]从大到小排序即可。

C.Adding Powers(1)
预处理范围内所有的ki,然后将数组排序后从大到小依次考虑用ki去消即可,可以满足贪心的想法,要注意0的干扰,重复的0是没有关系的。

D.Count the Arrays(0)
题意:构造一个一个先单调递增再单调递减的数组,且其中只有两个数字相同。(现在才发现,这种题目就是摆明了告诉你排列组合推公式,找规律什么的太笨了)。
1.考虑数组选取,m个数字中选n-1个数字:C(n-1,m);
2.考虑选取重复的数字:(n-2);
3.考虑数字的摆放,C(n-3,n-3)+C(n-4,n-3)+…+C(0,n-3) = 2n-3;
可以发现三者组合就得到了答案:C(n-1,m)*(n-2)*2n-3

#include 
using namespace std;
typedef long long ll;
const int N = 4e5+5;
const ll M = 998244353;

inline ll qpow(ll a,ll b){
	if(b < 0) return 1; // n-3可能小于0,需要特判
	ll res = 1;
	a %= M;
	while(b){
		if(b & 1) (res *= a) %= M;
		(a *= a) %= M;
		b >>= 1;
	}
	return res%M;
}

inline ll C(ll n,ll m){
	if(n == 0 || n == m) return 1;
	ll tm = 1,tn = 1;
	for(ll i = m;i >= m-n+1;--i) (tm *= i) %= M;
	for(ll i = 1;i <= n;++i) (tn *= i) %= M;
	return tm*qpow(tn,M-2)%M;
}

int main()
{	
	ll n,m;
	scanf("%lld%lld",&n,&m);
	printf("%lld\n",C(n-1,m)%M*qpow(2,n-3)%M*(n-2)%M);
	return 0;
} 

你可能感兴趣的:(萌新级)