D. Strange Fractions 数论签到
E. Strange Integers 签到,贪心排序
G. Edge Groups 组合数学,树形dp,思维,双阶乘
H. Life is a Game 克鲁斯卡尔重构树 ,倍增,并查集
I. Steadily Growing Steam DP,状态跳转
J. Two Binary Strings Problem Bitset 思维
K. Circle of Life zz构造
M. Harmony in Harmony zz构造
p/q = (a^2+b^2)/ab 数论做法枚举ab的时候,复杂度其实是不对的,加特判会擦边水过。
可以直接让b=2q,a直接等于上面分子,也就是判断是否是平方数
#include <bits/stdc++.h> using namespace std; typedef long long int ll; int pos[10000000+10]; int a[10000000+10]; int main(){ for(int i=1;i*i<=10000000;i++) { pos[i*i]=i; a[i]=i*i; } int t; cin>>t; while(t--) { int p,q; scanf("%d%d",&p,&q); if(p<2*q) { cout<<0<<" "<<0<<'\n'; continue; } int g=__gcd(p,q); p/=g; q/=g; int flag=0; for(int i=1;a[i]<p;i++) { int fuck=p-a[i]; if(pos[fuck]) { if((ll)pos[fuck]*(ll)i==q) { flag=1; cout<<pos[fuck]<<" "<<i<<'\n'; break; } } } if(!flag) { cout<<0<<" "<<0<<'\n'; } } return 0; }
E. Strange Integers
选择m个,任意差值都要>=k,也就是只需要满足大系大小关系紧邻的全部都>=k即可。
直接排序,贪心选择即可
#include <bits/stdc++.h> using namespace std; using ll = long long; ll a[100000+10]; int main() { ll n,k; cin>>n>>k; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); } sort(a+1,a+1+n); ll pre=a[1]; int ans=1; for(int i=2;i<=n;i++) { if(a[i]>=pre+k) { ans++; pre=a[i]; // cout<<a[i]<<" "; } } cout<<ans; return 0; }
G. Edge Groups
一个关键结论是,节点i的子树若有偶数条边,那么一定要把这些边两两配对。否则全局配对就会破坏。而如果有奇数条边,也只能拽过去i和i父亲连接的边来弥补。
所以问题就变成了,统计儿子节点的匹配情况,如果剩一条边没有匹配,那么这条边就锁死。否则,对剩下的自由边进行两两匹配,这个用到组合数学一点知识,方案数是双阶乘,1*3*5...。如果当前自由边是偶数个,就必须匹配完,否则同样会对父亲造成影响。如果是奇数,就一定会剩下一个。只能通过拽去父亲和自己的边来弥补。
# include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll fac[100000+10];
# define mod 998244353
void init()
{
fac[1]=1;
fac[0]=1;
for(int i=2;i<=100000;i++)
{
if(i%2==1)
{
fac[i]=fac[i-1]*(ll)i%mod;
}
else
{
fac[i]=fac[i-1];
}
}
}
int sizeson[100000+10];
ll dp[100000+10];
vector<int>v[100000+10];
void dfs(int now,int pre)
{
//sizeson 存的是当前边的数量
int flag=0;
ll nowans=1;
int nowtot=0;
dp[now]=1;
for(auto it:v[now])
{
if(it==pre)
continue;
flag=1;
dfs(it,now);
if(sizeson[it]%2==0)
{
nowtot++;
}
nowans*=dp[it];
nowans%=mod;
}
dp[now]=nowans*fac[nowtot];
dp[now]%=mod;
sizeson[now]=nowtot%2;
if(flag==0)
{
sizeson[now]=0;
dp[now]=1;
}
}
int main()
{
init();
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,0);
cout<<dp[1];
return 0;
}
套路先跑重构树,但是建边有讲究。
每次合并两个单点x,y,就设置一个虚点1,权值为a[x]+a[y].边权分别是w-a[x],w-a[y],w为x,y之间边权。
又有两个单点a,b, 设置虚点2,权值为a[a]+a[b],边权等等,
若合并虚点1,虚点2,边权为分别为w-a[a]-a[b] ,w-a[x]-a[y] ,虚点3,为合并虚点1,2的结果
那么,我们从单点x到虚点3的路径,有两条边,边权分别为w-a[x],w-a[x]-a[y]
若k>=w-a[x]意味着我们可以到达y,当k>=w-a[x]-a[y],意味着我们可以通过x,y集合和a,b集合的一条边到达a,b中的某一个,然而,我们重构树连边过程中,边权从小到大,在合并两个虚点的时候,已经合并了虚点管辖的单点,意味着k>=w-a[x]-a[y]的时候,一定能到达a,b二者。
然后就是重构树常规操作。对给定点x进行倍增的时候,倒叙遍历,只要能跳就跳,越往上,a[i]权值之和越高。
#include <bits/stdc++.h> using namespace std; typedef long long int ll; # define mod 1000000007 ll a[200000+10]; int f[200000+10]; int getf(int x) { if(x==f[x])return x; else { f[x]=getf(f[x]); return f[x]; } } struct node { int b,e; ll w; }; struct node s[200000+10]; bool cmp(struct node x, struct node y) { return x.w<y.w; } ll maxx[200000+10][31]; int ans[200000+10][31],m,n,tot; void work() { for(int i=1;i<=m;i++) { int t1=getf(s[i].b),t2=getf(s[i].e); if(t1==t2) continue; tot++; a[tot]=a[t1]+a[t2]; f[t1]=tot; f[t2]=tot; ans[t1][0]=tot; ans[t2][0]=tot; maxx[t1][0]=s[i].w-a[t1]; maxx[t2][0]=s[i].w-a[t2]; } } int main() { int q; cin>>n>>m>>q; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); f[i]=i; } for(int i=n+1;i<=2*n;i++) { f[i]=i; } for(int i=1;i<=m;i++) { scanf("%d%d%lld",&s[i].b,&s[i].e,&s[i].w); } tot=n; sort(s+1,s+1+m,cmp); work(); a[0]=a[tot]; for(int j=1;j<=30;j++) { for(int i=1;i<=tot;i++) { maxx[i][j]=max(maxx[ans[i][j-1]][j-1],maxx[i][j-1]); ans[i][j]=ans[ans[i][j-1]][j-1]; } } while(q--) { int x,k; scanf("%d%d",&x,&k); for(int i=30;i>=0;i--) { if(maxx[x][i]<=k) { x=ans[x][i]; } } cout<<k+a[x]<<'\n'; } return 0; }
I. Steadily Growing Steam
dp[0/1][i][j] 代表差值为i(较大者减去较小)并且选了k个*ti的最大v之和。
细节还是不少的。
#include <bits/stdc++.h> using namespace std; using ll = long long; const int N = 2607, M = 107; ll v[N], t[N]; ll dp[3][N][M]; int main() { int n, kk; scanf("%d%d", &n, &kk); for (int i = 1; i <= n; i++) { scanf("%lld%lld", &v[i], &t[i]); } for (int i = 0; i < N; i++) { for (int j = 0; j <= kk; j++) { dp[0][i][j] = dp[1][i][j] = -1e17; } } dp[1][0][0] = 0; ll mx = 0; int now = 0; for (int i = 1; i <= n; i++) { for (int j = 0; j <= N; j++) { for (int k = 0; k <= kk; k++) { dp[now][j][k] = max(dp[now][j][k], dp[!now][j][k]); } for (int k = 0; k <= kk; k++) { dp[now][abs(j - t[i])][k] = max(dp[now][abs(j - t[i])][k], dp[!now][j][k] + v[i]); if (k < kk) { dp[now][abs(j - 2 * t[i])][k + 1] = max(dp[now][abs(j - 2 * t[i])][k + 1], dp[!now][j][k] + v[i]); } if (j + t[i] < N) dp[now][j + t[i]][k] = max(dp[now][j + t[i]][k], dp[!now][j][k] + v[i]); if (k < kk && j + 2 * t[i] < N) { dp[now][j + 2 * t[i]][k + 1] = max(dp[now][j + 2 * t[i]][k + 1], dp[!now][j][k] + v[i]); } } } now = !now; } ll res = 0; for (int j = 0; j <= kk; j++) { res = max(res, dp[0][0][j]); res = max(res, dp[1][0][j]); } printf("%lld\n", res); return 0; }
J. Two Binary Strings Problem
1当成1,0当成-1,所以当目标值为1的时候,前缀和必须大于0
当目标值为0的时候,前缀和必须小于等于0
按照前缀和从大到小排序,设当前位置为i
b值为1时
在之前出现的前缀和全都大于等于他(相同的时候按照坐标排序)
这些j代表的k是不合法的
b值为0的时候
在此之前出现的前缀和都大于等于它,大于等于的话肯定对,
且我们排序规则已经强制了我们已经处理了前缀和等于的情况
只需要提取出全部没出现的j
当前前缀和<=0 但是为1的时候,若前面没有比他大的
当前前缀额>0 但是为0的时候,若后面没有比他小的且靠前的
A是之前处理的下标集合,B是全集,C是错误的k的集合。每次A插入一个下标x,对n-x的位置标记为1,在获取x之前不合法的k时候,右移(n-p)就会把(n-p+1)开始的全部下标变为1开始的,也就是k的长度。这样做发现还是缺乏正确性。原因在于题目一个特殊条件这个max使我们再统计前缀和的时候,如果一个前缀和(1开始的)不满足bi,那么>=i的k值就1全都不符合了。如果不加这一条件,我们只会把这一个单点给排除,但实际上,以后全部的k都不满足了
#include<bits/stdc++.h> using namespace std; typedef long long ll; int a[500000+10]; bitset<50010>A,B,C; int sum[500000+10],id[500000+10]; bool cmp(int x,int y) { if(sum[x]!=sum[y]) return sum[x]>sum[y]; return x<y; } int main() { int t; cin>>t; while(t--) { int n; cin>>n; string s; cin>>s; s=" "+s; A.reset(); B.reset(); C.reset(); string b; cin>>b; b= " "+b; for(int i=1;i<=n;i++) { sum[i]=sum[i-1]+1; if(s[i]=='0') sum[i]-=2; id[i]=i; B[i]=1; } B[0]=1; id[0]=0; sort(id,id+n+1,cmp); int minn=1e9; for(int i=0;i<=n;i++) { int p=id[i]; if(p==0) { A[n-p]=1; continue; } if(b[p]=='1') { C|=(A>>(n-p)); } else { C|=((A^B)>>(n-p)); } if(b[p]=='1'&&sum[p]<=0) minn=min(minn,p); if(b[p]=='0'&&sum[p]>0) minn=min(minn,p); A[n-p]=1; } for(int i=1;i<=n;i++) { if(C[i]||i>=minn) { cout<<0; } else { cout<<1; } } cout<<'\n'; } return 0; }
K. Circle of Life M. Harmony in Harmony
银牌题出俩总共不到五行的构造,不得不说是很不合理的,甚至算得上是出题人的失误。