noip模拟18
A 图 (a)
好像很简单,但是想了很久。
考虑一条边,最终能保留下来的条件,就是被操作了奇数次。又由于 \(m\) 最大只有 \(64\),正好能用一个 ull
存下来,第 \(i\) 位表示第 \(i\) 次操作,是否属于 \(S\) 或 \(T\) 集合。
然后枚举每一条边,若边 \((x,y)\) 的两节点分别在 \(S\) 或 \(T\) 中出现了奇数次,那么这条边是可取的。统计即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N=1e4+4;
int n,m;
int s[N],t[N];
signed main()
{
// freopen("in.in","r",stdin);
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
string c;cin>>c;
for(int j=0;j<n;j++)
{
// int now=c[j]
if(c[j]=='1'||c[j]=='3') s[j+1]|=(1ull<<i);
if(c[j]=='2'||c[j]=='3') t[j+1]|=(1ull<<i);
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
ans+=(__builtin_popcountll((s[i]&t[j])|(s[j]&t[i]))%2);
cout<<ans;
}
B 序列 (b)
发现一个判断贪心题的好方法:如果题目要求最大值后取模,并且最大值可能极大,那基本上选取策略就是固定的了。不会有答案结果大小的优劣抉择。
这道题就是这样。我们把对于同一个数的所有修改操作只留下一个最优的,其他舍弃。假设剩下的参数为 \(y\),再把这个操作等价成给 \(a_i\) 加上 \(y-a_i\)。这样就只留下两种操作了。
考虑给 \(a_i\) 加上一个数,等于乘以 \(\frac{a_i+y}{a_i}\),这样就只剩下一种操作了。
乘法操作就从大往小取就行了,先乘大的数肯定更好。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
const int N=1e5+5;
int a[N];
int ans[N];
struct node{
int op,x,y;
}b[N];
int now[N];
const int mod=1e9+7;
int ad[N];
vector<int>e[N];
priority_queue<pair<double,int> >q;
int ppow(int a,int b)
{
int res=1;
while(b)
{
if(b&1) res=(res*a)%mod;
a=(a*a)%mod,b>>=1;
}return res;
}
signed main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i].op>>b[i].x>>b[i].y;
int qq=0;
for(int i=1;i<=m;i++)
{
if(b[i].op==1)
{
// if(b[i].y-a[b[i].x]<ad[b[i].x])++q;
ad[b[i].x]=max(ad[b[i].x],b[i].y-a[b[i].x]);
}if(b[i].op==2) e[b[i].x].push_back(b[i].y);
if(b[i].op==3) q.push({b[i].y,-b[i].y});
}
int ans=1;
for(int i=1;i<=n;i++)
{
if(ad[i]) e[i].push_back(ad[i]);
ans=(ans*a[i])%mod;
}
for(int i=1;i<=n;i++)
{
// cout<<e[i].size()<<" ";
if(!e[i].size()) continue;
sort(e[i].begin(),e[i].end());
q.push({1.0*(e[i].back()+a[i])/(1.0*a[i]),i});
// e[i].pop_back();
}
// cout<<"dic\n";
cout<<ans<<" ";
while(!q.empty())
{
++qq;
int now=q.top().second;q.pop();
if(now<0)
{
now=-now;
ans=(ans*now)%mod;
}
else
{
ans=ans*(a[now]+e[now].back())%mod*ppow(a[now],mod-2)%mod;
a[now]+=e[now].back(),e[now].pop_back();
if(e[now].size())
q.push({1.0*(e[now].back()+a[now])/(1.0*a[now]),now});
}
cout<<ans<<" ";
}
for(int i=qq+1;i<=m;i++) cout<<ans<<" ";
}
C 树 (c)
先手非常能赢,输出 \(n^{2D}\) 可获得 \(90\) 分。加上 \(D=1\) 的 \(O(n)\) dp:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+5;
int u,v,now,f[N],g,fa[N],dep[N],cnt[2];
const int mod=1e9+7;
bool fg[N],dp[N],wd[N],tag[N];
long long b,ans,num;
vector<int>e[N];
void dfs(int u)
{
cnt[dep[u]]++;
for(int v:e[u])
{
if(v!=fa[u])
{
dep[v]=dep[u]^1;
fa[v]=u,dfs(v);
if(!fg[v])fg[u]=1;
}
}
}
int ppow(int a,int b)
{
int res=1;
while(b)
{
if(b&1) res=(res*a)%mod;
a=(a*a)%mod,b>>=1;
}return res;
}
int check(int u)
{
int res=0;
for(int v:e[u])
{
if(v!=fa[u]&&!fg[v])res++;
}
if(fa[u])res+=(!dp[fa[u]]);
return res;
}
int check0(int u)
{
int res=0;
for(int v:e[u])
{
if(v!=fa[u]&&!fg[v])res++;
}
return res;
}
void dfsp(int u)
{
int res=check(u);
for(int v:e[u])
{
if(v!=fa[u])
{
fa[v]=u;
if(fg[v])dp[v]=1;
else dp[v]=(res==1);
dfsp(v);
}
}
}
void dfs0(int u)
{
tag[u]=1;
if(!fg[u]) num++;
for(int v:e[u])
{
if(v!=fa[u])
{
if(fg[u])
{
if(!fg[v])dfs0(v);
}
else if(fg[v]&&wd[v])dfs0(v);
}
}
}
signed main()
{
// freopen("c0.in","r",stdin);
// freopen("c.out","w",stdout);
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
int n,m;cin>>n>>m;
for(int i=1;i<n;i++)
{
cin>>u>>v,e[u].push_back(v),e[v].push_back(u);
}
if(m>1){
return cout<<ppow(n,2*m),0;
}
dfs(1),dp[1]=fg[1],dfsp(1);
for(int i=1;i<=n;i++)
{
if(fg[i]&&check0(i)==1) wd[i]=1;
}
if(!fg[1]||wd[1])dfs0(1);
if(!fg[1])
{
for(int i=1;i<=n;i++) if(!dp[i])ans=(ans+num)%mod;
}
else
{
ans=(1LL*n*n)%mod;
for(int i=1;i<=n;i++)
if(!dp[i])ans=(ans-num+mod)%mod;
}
cout<<ans;
return 0;
}