题目链接
签到
签到
题意:
求将树边分解成两两匹配的方案数
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10;
const int INF=1e9;
const int mod=998244353;
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1){
if(b&1)ans=ans%mod*a%mod;
a=a%mod*a%mod;
}
return ans;
}
int sum[N],n,fac[N],inv_fac[N];
vector<int> adj[N];
int dp[N][2];
int C(int n,int m){
return fac[n]*inv_fac[m]%mod*inv_fac[n-m]%mod;
}
void init(){
fac[0]=inv_fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod,inv_fac[i]=qpow(fac[i],mod-2);
sum[2]=C(2,2),sum[3]=C(3,2),sum[0]=sum[1]=1;
for(int i=4;i<=n;i++){
sum[i]=C(i,2)*sum[i-2]%mod;
}
for(int i=0;i<=n;i++)dp[i][0]=dp[i][1]=-1;
}
void dfs(int u,int fa){
if(u!=1&&adj[u].size()==1){
dp[u][0]=1;
return;
}
int cnt=0;
int res=1;
for(auto v:adj[u]){
if(v==fa)continue;
dfs(v,u);
if(dp[v][0]!=-1)res=res*dp[v][0]%mod,cnt++;
else res=res*dp[v][1]%mod;
}
// cout<
res=res*sum[cnt]%mod*inv_fac[cnt/2]%mod;
if(cnt&1)dp[u][1]=res;
else dp[u][0]=res;
}
void solve(){
cin>>n;
init();
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs(1,0);
// for(int i=1;i<=n;i++)cout<
// for(int i=1;i<=n;i++)cout<
cout<<dp[1][0]<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
思路:
带负权的背包问题,直接转移即可
d p i , j , k 表示枚举到第 i 个物品,操作了 j 次,背包容量为 k 的最大值 dp_{i,j,k}表示枚举到第i个物品,操作了j次,背包容量为k的最大值 dpi,j,k表示枚举到第i个物品,操作了j次,背包容量为k的最大值
为了防止背包容量为负,需要设置一个偏移量为p,表示背包容量为0的位置
答案: m a x i = 0 k ( d p n , i , p ) max_{i=0}^k(dp_{n,i,p}) maxi=0k(dpn,i,p)
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1e5+10;
const int INF=1e16;
const int mod=998244353;
int dp[110][110][2610],n,k,v[110],c[110];
void solve(){
cin>>n>>k;
for(int i=0;i<=n;i++){
for(int j=0;j<=k;j++){
for(int u=0;u<=2600;u++)dp[i][j][u]=-INF;
}
}
dp[0][0][1300]=0;
for(int i=1;i<=n;i++)cin>>v[i]>>c[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++){
for(int u=0;u<=2600;u++){
if(dp[i-1][j][u]==-INF)continue;
int tmp=dp[i-1][j][u]+v[i];
if(u+c[i]<=2600)dp[i][j][u+c[i]]=max(dp[i][j][u+c[i]],tmp);
if(u-c[i]>=0)dp[i][j][u-c[i]]=max(dp[i][j][u-c[i]],tmp);
if(j+1<=k&&u+2*c[i]<=2600)dp[i][j+1][u+2*c[i]]=max(dp[i][j+1][u+2*c[i]],tmp);
if(j+1<=k&&u-2*c[i]>=0)dp[i][j+1][u-2*c[i]]=max(dp[i][j+1][u-2*c[i]],tmp);
dp[i][j][u]=max(dp[i][j][u],dp[i-1][j][u]);
}
}
}
int ans=-INF;
for(int i=0;i<=k;i++)ans=max(ans,dp[n][i][1300]);
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
⼀张带边权带点权⽆向图。从某点出发,有初始声望。
每第⼀次到达⼀个点将获得点权等值的声望加成。
经过⼀条边需要满⾜边权等值的最低声望限制。
多次给出起点和初始声望,询问能达到的最⼤声望。
思路:
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
const int INF=1e16;
const int mod=998244353;
struct Edge{
int u,v,w;
bool operator<(const Edge&tmp)const{
return w<tmp.w;
}
};
struct DSU{
vector<int> par;
void init(int n){
par.resize(n);
iota(par.begin(),par.end(),0);
}
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
bool same(int x,int y){
return par[find(x)]==par[find(y)];
}
bool merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return false;
par[y]=x;
return true;
}
} dsu;
vector<Edge> e;
int n,m,q,tot,a[N],w[N],sum[N],par[N][30],cost[N][30];
void Ex_kruscal(){
for(auto [u,v,c]:e){
int fu=dsu.find(u),fv=dsu.find(v);
if(fu==fv)continue;
++tot;
a[tot]=a[fu]+a[fv];
w[tot]=c;
par[fu][0]=par[fv][0]=tot;
cost[fu][0]=w[tot]-a[fu];
cost[fv][0]=w[tot]-a[fv];
dsu.par[fu]=dsu.par[fv]=tot;
}
}
void solve(){
cin>>n>>m>>q;
tot=n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
e.push_back({u,v,w});
}
sort(e.begin(),e.end());
dsu.init(2*n+1);
Ex_kruscal();
for(int j=1;j<=25;j++){
for(int i=1;i<=tot;i++)par[i][j]=par[par[i][j-1]][j-1],cost[i][j]=max(cost[i][j-1],cost[par[i][j-1]][j-1]);
}
// for(int i=1;i<=tot;i++)cout<
while(q--){
int x,k;
cin>>x>>k;
for(int i=25;i>=0;i--){
if(cost[x][i]<=k&&par[x][i])x=par[x][i];
}
cout<<a[x]+k<<"\n";
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
更容易想到的做法:离线,启发式合并
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
const int INF=1e16;
const int mod=998244353;
struct Edge{
int u,v,w;
bool operator<(const Edge&tmp)const{
return w<tmp.w;
}
};
struct DSU{
vector<int> par;
void init(int n){
par.resize(n);
iota(par.begin(),par.end(),0);
}
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
bool same(int x,int y){
return par[find(x)]==par[find(y)];
}
bool merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return false;
par[y]=x;
return true;
}
} dsu;
vector<Edge> e;
int n,m,q,a[N],w[N];
set<array<int,2>> s[N];
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++)cin>>a[i],w[i]=a[i];
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
e.push_back({u,v,w});
}
sort(e.begin(),e.end());
vector<int> ans(q+1);
for(int i=1;i<=q;i++){
int x,k;
cin>>x>>k;
s[x].insert({k,i});
}
dsu.init(n+1);
auto merge=[&](int u,int v){
if(s[u].size()<s[v].size())swap(u,v);
w[u]+=w[v];
dsu.merge(u,v);
for(auto it:s[v])s[u].insert(it);
s[v].clear();
};
for(auto [u,v,c]:e){
int fu=dsu.find(u),fv=dsu.find(v);
if(fu==fv)continue;
while(s[fu].size()&&(*s[fu].begin())[0]+w[fu]<c){
auto [x,id]=*s[fu].begin();
s[fu].erase(s[fu].begin());
ans[id]=x+w[fu];
}
while(s[fv].size()&&(*s[fv].begin())[0]+w[fv]<c){
auto [x,id]=*s[fv].begin();
s[fv].erase(s[fv].begin());
ans[id]=x+w[fv];
}
merge(fu,fv);
}
for(int i=1;i<=n;i++){
while(s[i].size()){
auto [x,id]=*s[i].begin();
s[i].erase(s[i].begin());
ans[id]=x+w[i];
}
}
for(int i=1;i<=q;i++)cout<<ans[i]<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
将地分成n部分,第二次忘了前一次的分配,又将地分成n部分,求每个人两次分配的土地面积的交集最小值最大
思路:
神奇的构造题
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
const int INF=1e9;
const int mod=998244353;
void solve(){
int n;
cin>>n;
double ans=1.0/(1.0*n*((n+1)/2)*((n+2)/2));
cout<<setprecision(9)<<fixed<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
构造题
#include
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
const int INF=1e9;
const int mod=998244353;
void solve(){
int n;
cin>>n;
if(n==3)cout<<"Unlucky\n";
else{
int x=0,y=0,z=0;
if(n%2)z=1,n-=5;
y=n/4;
if(n%4!=0)x=1;
for(int i=1;i<=z;i++)cout<<"10001";
for(int i=1;i<=y;i++)cout<<"1001";
for(int i=1;i<=x;i++)cout<<"10";
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
题意:
给出 01 序列, A , B 给出01序列,A,B 给出01序列,A,B
定义 f ( l , r ) ,如果 1 的个数大于等于长度一半,则为 1 ,否则为 0 定义f(l,r),如果1的个数大于等于长度一半,则为1,否则为0 定义f(l,r),如果1的个数大于等于长度一半,则为1,否则为0
对于一个 k ,如果所有 i 满足 f ( m a x ( 1 , i − k + 1 ) , i ) = B i 则称 k 合法 对于一个k,如果所有i满足f(max(1,i-k+1),i)=B_i则称k合法 对于一个k,如果所有i满足f(max(1,i−k+1),i)=Bi则称k合法
输出一个长度为 n 的二进制,为 1 表示第 k 位合法 输出一个长度为n的二进制,为1表示第k位合法 输出一个长度为n的二进制,为1表示第k位合法
思路:
#include
using namespace std;
const int N=50010;
bitset<N> val[N],a,b,ans,all;
void solve(){
all.set();
int n;
cin>>n;
string s,t;
cin>>s>>t;
s=" "+s,t=" "+t;
vector<int> sum(n+1);
a.reset(),b.reset(),ans.set();
for(int i=0;i<=n;i++)val[i].reset();
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(s[i]=='1'?1:-1);
map<int,int> mp;
int idx=0;
auto id=[&](int x){
if(mp.count(x))return mp[x];
return mp[x]=++idx;
};
val[id(0)][0]=1;
b[0]=1;
for(int i=1;i<=n;i++){
if(sum[i]>sum[i-1])a^=val[id(sum[i-1])],b^=val[id(sum[i-1])];
else a^=val[id(sum[i])],b^=val[id(sum[i])];
if(t[i]=='1'){
bitset<N> tmp=a<<(n-i+1);
if(a[0])tmp|=(all>>(N-n+i-1));
ans&=tmp;
}
else{
bitset<N> tmp=b<<(n-i+1);
if(b[0])tmp|=(all>>(N-n+i-1));
ans&=tmp;
}
val[id(sum[i])][i]=1;
b[i]=1;
}
for(int i=n;i>=1;i--)cout<<ans[i];
cout<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
cin>>T;
while(T--)solve();
return 0;
}