比赛传送门
作者: fn
题目大意
k k k 维空间中,求最大的 n n n ,使得把 n n n 个点染色为黑白两种颜色时,可以被一个 k − 1 k-1 k−1 维超平面分成两半,每一半颜色相同。
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;
}
题目大意
给定 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;
}
题目大意
在 n ∗ n ∗ n n*n*n n∗n∗n 的三维空间里放 1 ∗ 1 ∗ 1 1*1*1 1∗1∗1 的小立方体(考虑重力,放在顶端时立方体会自由下落),使得三视图都是 n ∗ n n*n n∗n 的正方形。在每个位置放置的代价为 x ∗ y 2 ∗ z x*y^2*z x∗y2∗z ,求最大和最小代价。
考察内容
数学知识,贪心
分析
记底面代价为 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=X∗n2
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;
}
来源
洛谷 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) (R≥L) , 满足子段的众数出现次数严格大于其他整数的出现次数。
样例输入
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;
}
题目大意
如果两个相同长度的字符串的不同的字符不超过 k k k 个,我们称这两个字符串满足k-匹配。
给定一个长度为 n n n 的字符串 S S S 和一个整数 k k k 。对于每个 i ∈ [ 1 , n − 1 ] i∈[1,n-1] i∈[1,n−1] ,将 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+L−1] 与 S [ j , j + L − 1 ] S[j,j+L-1] S[j,j+L−1] 满足k-匹配的最大L 。
考虑如何计算所有的 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,t−1] 和 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=1∑t−1j=t∑nG[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],t−i) 。
不妨从大到小枚举 t t t ,过程中维护G对答案的贡献
注意到 G [ i ] [ j ] G[i][j] G[i][j] 的值不超过 t − i t-i t−i 。因此可以对每个 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]∣2≤j≤n} 中此时对答案有贡献且值为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]∣2≤j≤n} 中对答案有贡献的最大值。对于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;
}
题目大意
给定一个有 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=1∑nW[i][k]W[i][j](k=1∑nW[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} A−1 矩阵,套矩阵求逆板子
#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;
}