[ABC221F] Diameter set - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:树的直径,中心点。
关键点1: 分奇偶讨论:中心点为边的情况要以中心边左右两个点分为两个集合。 中心点为一个点的情况,会出现多个集合,要以"和中心点直接相连的点"分为不同的集合!!! 关键点2:怎么计算答案,这又用到了数学的知识. 当把点分为大小不同的集合之后,就是怎么选点,组合出不同的答案了 首先中心点为边的情况很明显,只能选两个点,那么就是乘法原理:左集合大小乘右集合大小. 当中心点为一个点时,假设有四个集合,大小为:3,3,3,6. 那么怎么计算选两个点的答案贡献?选三个点的答案贡献?选四个点的答案贡献?只有四个集合,最多只能选四个点,每个集合单次只能选一个点。 每个集合可选的情况为点的个数+1,why? 因为给每个集合加了一个0点,选了0点意味着不选这个集合. 那么根据乘法原理:有所有组合情况(3+1)*(3+1)*(3+1)*(6+1) 但是题目说至少要选两个点,那么上面算的情况是多算了的,多算了多少呢? 只选了一个点的情况是不合法的,一共有3+3+3+6种(x,0,0,0),因为每个点都可以作为那个只选的点x. 还要再减去1,因为什么都不选(0,0,0,0)也被算上了,这是不合法的. 所以这种情况的答案为(s1+1)*(s2+1)*(s3+1)*(s4+1)-(s1+s2+s3+s4)-1.
const int mod=998244353;
int n;
vector<int> vct[200005];
int S,T,d,heart1,heart2; 其中一条直径的始点和终点,以及直径的一半距离,以及两个中心点
int maxn=0,curnode;
int road[200005],dis[200005];
vector<int> ans;
void dfs1(int s,int depth,int fa){ 找到其中一条直径的起点和终点 o(n)
if(depth>maxn) maxn=depth,curnode=s;
for(auto v:vct[s]) if(v!=fa) dfs1(v,depth+1,s);
}
void bfs1(int s){ 确定直径的路径,通过路径可以得知中心点. o(n)
queue<int> que;
que.emplace(s);
while(que.size()){
int from=que.front();
que.pop();
if(from==T) break;
for(auto v:vct[from]){
int to=v;
if(dis[to]==0&&to!=s){
dis[to]=dis[from]+1;
que.emplace(to);
road[to]=from;
}
}
}
}
int dfs2(int s,int depth,int fa){ 处理一个中心点的情况,o(n)
特别留意d==1的情况,图太小了,没有按照所想那样有个间接点,直接就是点本身了.
if(d==1) return 1; 再写一行
if(depth==d-1) return (int)vct[s].size()-1;
int res=0;
for(auto v:vct[s]) if(v!=fa) res+=dfs2(v,depth+1,s);
return res;
}
int dfs3(int s,int depth,int fa){ 处理两个中心点的情况,o(n)
if(depth==d-1) return vct[s].size()-1;
int res=0;
for(auto v:vct[s]) if(v!=fa) res+=dfs2(v,depth+1,s);
return res;
}
[ABC221F] Diameter set
https://www.luogu.com.cn/problem/AT_abc221_f
关键点1:
分奇偶讨论:中心点为边的情况要以中心边左右两个点分为两个集合。
中心点为一个点的情况,会出现多个集合,要以"和中心点直接相连的点"分为不同的集合!!!
关键点2:怎么计算答案,这又用到了数学的知识.
当把点分为大小不同的集合之后,就是怎么选点,组合出不同的答案了
首先中心点为边的情况很明显,只能选两个点,那么就是乘法原理:左集合大小乘右集合大小.
当中心点为一个点时,假设有四个集合,大小为:3,3,3,6.
那么怎么计算选两个点的答案贡献?选三个点的答案贡献?选四个点的答案贡献?只有四个集合,最多只能选四个点。
每个集合可选的情况为点的个数+1,why? 因为给每个集合加了一个0点,选了0点意味着不选这个集合.
那么根据乘法原理:有所有组合情况(3+1)*(3+1)*(3+1)*(6+1)
但是题目说至少要选两个点,那么上面算的情况是多算了的,多算了多少呢?
只选了一个点的情况是不合法的,一共有3+3+3+6种(x,0,0,0),因为每个点都可以作为那个只选的点x.
还要再减去1,因为什么都不选(0,0,0,0)也被算上了,这是不合法的.
所以这种情况的答案为(s1+1)*(s2+1)*(s3+1)*(s4+1)-(s1+s2+s3+s4)-1.
void solve(){ o(n)
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v; cin>>u>>v;
vct[u].emplace_back(v);
vct[v].emplace_back(u);
}
if(n==2) { 特判一下小情况
cout<<"1";
return;
}
maxn=0,dfs1(1,0,0),S=curnode;
maxn=0,dfs1(S,0,0),T=curnode;
bfs1(S);
树的直径长度奇偶不同,情况不同,分奇偶讨论
if(dis[T]&1){ 中心为两个点加中间一条边
int cur=T;
d=dis[T]/2;
while(d--) cur=road[cur];
d=dis[T]/2;
heart1=cur,heart2=road[cur];
int ask1=dfs3(heart1,0,heart2);
int ask2=dfs3(heart2,0,heart1);
cout<<ask1*ask2%mod;
}
else{ 中心为一个点,以"和中心点直接相连的点"分为不同的集合!!
int cur=T;
d=dis[T]/2;
while(d--) cur=road[cur];
d=dis[T]/2;
heart1=cur;
for(auto v:vct[heart1]) ans.emplace_back(dfs2(v,1,heart1));
int ansss=1,sum=0;
for(auto a:ans){
ansss*=(a+1),ansss%=mod;
sum+=a,sum%=mod;
}
if(ans.size()!=0) cout<<(ansss-sum-1+mod)%mod;
else cout<<0;
}
}
[ABC272E] Add and Mex - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)\
思路:
调和级数.(暴力) 易知答案的区间为[0,n],因为最多n个数字全都不一样,即ai取值为[0,n-1]是有效的 负数和大于等于n的不用考虑,那么考虑正数的话数字i作为贡献的次数有n/1,n/(i+2),n/(i+3)...n/n. 那么m次操作,每个数字被遍历到的次数的总和,就是总遍历次数,为n/1+n/2+n/3+...n/n=n(1+1/2+1/3+...+1/n) 其中(1+1/2+1/3+...+1/n)为调和级数,约为logn,,所以总复杂度约为o(nlogn) 实现写拉了。。不要一次一次枚举m然后考虑每个数字. 应该单独考虑每一个数字对每一次m的贡献.vis[i][j]为第i次操作,拥有的数字j标记为vis[i][j]=1.否则为0.
int n,m;
//vector<int> vis[200005]; 两种vis都行,这个要排序
unordered_map<int,int> vis[200005];
思路:调和级数.
易知答案的区间为[0,n],因为最多n个数字全都不一样,即ai取值为[0,n-1]是有效的
负数和大于等于n的不用考虑,那么考虑正数的话数字i作为贡献的次数有n/1,n/(i+2),n/(i+3)...n/n.
那么m次操作,每个数字被遍历到的次数的总和,就是总遍历次数,为n/1+n/2+n/3+...n/n=n(1+1/2+1/3+...+1/n)
其中(1+1/2+1/3+...+1/n)为调和级数,约为logn,,所以总复杂度约为o(nlogn)
实现写拉了。。不要一次一次枚举m然后考虑每个数字.应该单独考虑每一个数字对每一次m的贡献.vis[i][j].
Add and MEX
https://www.luogu.com.cn/problem/AT_abc272_e
void solve(){ F 调和级数..
cin>>n>>m;
for(int i=1;i<=n;i++){
int x; cin>>x;
int begin=1;
if(x<0) begin=((abs(x)-1)/i+1); 如果是负的,算出起点
for(int j=begin;j<=m;j++){
if(x+j*i>=n) break;
vis[j][x+j*i]=1;
}
}
for(int j=1;j<=m;j++){ 看着很恐怖的m*n循环,但是实际上是完全跑不满的
int cur=0;
while(vis[j][cur]) cur++;
cout<<cur<<endl;
}
}
[ABC248C] Dice Sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:赛时没出的简单多重背包,dp[i][j]定义为填i个数字总和为j的方案数.
const int mod=998244353;
int n,m,k;
int dp[55][2505]; dp[i][j]定义为填i个数字总和为j的方案数
Dice Sum
https://www.luogu.com.cn/problem/AT_abc248_c
void solve(){ 补B--多重背包
cin>>n>>m>>k;
int ans=0;
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
for(int h=1;h<=m&&h<=j;h++){
(dp[i][j]+=dp[i-1][j-h])%=mod;
}
if(i==n) (ans+=dp[i][j])%=mod;
}
}
cout<<ans;
}
[ABC178C] Ubiquity - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:简单的容斥题,但是这方面基础太差,想错了。
全部情况为10^n,没有0的情况为9^n,没有1的情况为9^n,多减了[1,8]的情况+8^n 有一个经典错法:A22*10^(n-2),选两个位置作为09,其他位置随便填,这样算是会算重的 例如:09.....90第一个09为A22的其中一种取值,其他位置随便填.最后一个90是随便填的结果 后面又有09.....90,最后一个90为A22其中一种取值,其他位置随便填.第一个09是随便填的结果 这样就有重了.
const int mod=1e9+7;
int quickpow(int a,int b){
int res=1;
while(b){
if(b&1) res*=a,res%=mod;
a*=a,a%=mod;
b>>=1;
}
return res;
}
int n;
思路:容斥,10^n-9^n-9^n+8^n
全部情况为10^n,没有0的情况为9^n,没有1的情况为9^n,多减了[1,8]的情况+8^n
有一个经典错法:A22*10^(n-2),选两个位置作为09,其他位置随便填,这样算是会算重的
例如:09.....90第一个09为A22的其中一种取值,其他位置随便填.最后一个90是随便填的结果
后面又有09.....90,最后一个90为A22其中一种取值,其他位置随便填.第一个09是随便填的结果
这样就有重了.
Ubiquity
void solve(){ 补C
cin>>n; cout<<((quickpow(10,n)-quickpow(9,n)*2%mod+mod)%mod+quickpow(8,n))%mod;
}
[ABC263D] Left Right Operation - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:自己的写法是完全没问题的,但是ac得有点灵异。
正解比较合规:设x,y为左,右改变的个数,pre为原数组的前缀和数组
那么答案为L*x+R*y+pre[n-y]-pre[x].
考虑枚举x,那么L*x-pre[x]为定值,R*y+pre[n-y]考虑取最小,y的取值为[0,n-x]
那么可以o(n)预处理前缀和数组minn[j]代表y的取值为[0,j]时可以取的最小值.
int n,l,r;
int arr[200005];
int pre[200005],suf[200005]; 能减少的值
int mxpre[200005],mxsuf[200005]; 到达i点,最大的pre值位置为mxpre[i],最大suf值位置为mxsuf[i]
Left Right Operation
https://www.luogu.com.cn/problem/AT_abc263_d
void solve(){ E 灵异。。为什么错误的循环范围才ac??why
cin>>n>>l>>r;
int sum=0,maxn=0;
for(int i=1;i<=n;i++) cin>>arr[i],sum+=arr[i];
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+(arr[i]-l);
for(int i=n;i>=1;i--) suf[i]=suf[i+1]+(arr[i]-r);
int maxl=1,maxr=n;
for(int i=1;i<=n;i++){
if(pre[i]>pre[maxl]) maxl=i;
mxpre[i]=maxl;
}
for(int i=n;i>=1;i--){
if(suf[i]>suf[maxr]) maxr=i;
mxsuf[i]=maxr;
}
i=[0,n+1]就AC,不然会wa8个点... 但是mxpre[0]=0,pre[0]=0.n+1位置也都是0,那应该没有意义才对.why?
但是问题是i=0的时候会到else语句就越界了..
for(int i=0;i<=n+1;i++){ ----???
if(mxpre[i]!=mxsuf[i]) maxn=max(maxn,pre[mxpre[i]]+suf[mxsuf[i]]);
else maxn=max({maxn,pre[mxpre[i-1]]+suf[mxsuf[i]],pre[mxpre[i]]+suf[mxsuf[i+1]]});
}
cout<<sum-maxn;
}