待补
B. A Funny Bipartite Graph
C. And and Pair
题意:
给定一个很大的数n的二进制表示s(也就是01串),计算满足条件的数对<i,j>的数量
条件:
0<=j<=i<=n
i&n=i
i&j=0
其中&符号表示位运算与
答案对1e9+7取模
数据范围:|s|<=1e5
解法:
很容易看出可以用数位dp来解
当s[i]=1的时候,i可以为0或者1,i=0时j=0/1,i=1时j=0
当s[i]=0的时候,i只能为0,i=0时j=0/1
当存在最高位限制的时候,i=0时j=0,此时j不能为1
详见代码
需要注意的点是平常数位dp是从高位到低位dp,拆分数的时候一般把高位放在数组的右边
但是给定的01串高位在左边,低位在右边,需要改一下dp的顺序,或者将串翻转一下。
ps:
似乎还有其他解法,组合数学什么的
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
const int mod=1e9+7;
char s[maxm];
int d[maxm];
int dfs(int len,int limit){//limit是i的limit,j不需要limit
if(!len)return 1;
if(!limit&&d[len]!=-1)return d[len];
int ans=0;
if(s[len]==1){//i可0可1
if(limit){//10+limit,00+!limit,不能01
ans+=dfs(len-1,1)+dfs(len-1,0);
}else{//10,00,01
ans+=dfs(len-1,0)*3;
}
}else{//i只能0
if(limit){//00+limit,不能01
ans+=dfs(len-1,1);
}else{//00,01
ans+=dfs(len-1,0)*2;
}
}
ans%=mod;
if(!limit)d[len]=ans;
return ans;
}
int solve(){
int len=strlen(s+1);
reverse(s+1,s+1+len);//翻转一下,否则dp中串的访问顺序就反了
for(int i=1;i<=len;i++){//init
d[i]=-1;
s[i]-='0';
}
return dfs(len,1);
}
signed main(){
int T;cin>>T;
while(T--){
scanf("%s",s+1);
int ans=solve();
cout<<ans<<endl;
}
return 0;
}
E. Bob’s Problem
题意:
给定n个点m条边的无向图,和一个整数k
每条边是黑色或者白色,且每条边有边权
现在你必须在图中选择一些边,最多选择k条白边,
要求选择的边能使得图连通
问选择出的边的最大边权和是多少
如果无解则输出-1
数据范围:n<=5e4,m<=5e5
解法:
要使得边权和最大,因为黑边没有数量限制所以全选,并查集维护一下连通性,把白边存下来
然后白边按边权从大到小排序
遍历白边,如果白边两端不连通则选定且k-=1,否则先不选
如果k次机会选完了还是不连通则无解
如果连通之后k还有剩余,则优先把剩下的边权大的选下来
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e6+5;
struct E{
int x,y,w;
friend bool operator<(E a,E b){
return a.w>b.w;
}
};
vector<E>temp;
int mark[maxm];//表示被选定的白边
int pre[maxm];
int n,m,k;
int ffind(int x){
return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
signed main(){
int T;cin>>T;
while(T--){
cin>>n>>m>>k;
temp.clear();
for(int i=1;i<=n;i++){
pre[i]=i;
}
int ans=0;
for(int i=1;i<=m;i++){
int x,y,w,c;cin>>x>>y>>w>>c;
if(c==0){//黑边全选
ans+=w;
pre[ffind(x)]=ffind(y);//并查集维护图的连通性
}else{//白边存下来
temp.push_back({x,y,w});
}
}
sort(temp.begin(),temp.end());
int len=temp.size();
for(int i=0;i<len;i++){
mark[i]=0;
}
for(int i=0;i<len&&k;i++){
int x=ffind(temp[i].x);
int y=ffind(temp[i].y);
if(x!=y){
pre[x]=y;
ans+=temp[i].w;
mark[i]=1;
k--;
}
}
if(k){//如果k还有剩余,则贪心的把边权大的未选的选下
for(int i=0;i<len&&k;i++){
if(!mark[i]){
ans+=temp[i].w;
k--;
}
}
}
int cnt=0;
for(int i=1;i<=n;i++){
if(pre[i]==i){
cnt++;
if(cnt>1)break;
}
}
if(cnt!=1){
puts("-1");
}else{
cout<<ans<<endl;
}
}
return 0;
}
G. Eating Plan
题意:
给定长度为n的排列a,其中a[i]=k表示第i个盘子中的食物重量为(k的阶乘)
你的胃很奇怪,当吃了重量为x的食物的时候,剩下的食物质量为x%998857459
现在有m次询问,每次询问给出一个整数k,(保证1<=k<998857459)
你只能吃一个连续区间[L,R]的盘子中的食物,问使得胃中剩下的食物大于等于k的最短区间长度是多少
数据范围:n<=1e5,m<=1e4
解法:
一个简单的想法是枚举区间,计算区间和(用前缀和做),然后对同一区间长度能保留的最大重量取max,
如果存在区间长度为len1,最多保留v1,区间长度len2,最多保留v2,
如果len1<len2且v1>=v2,那么区间len1肯定更优,跳过区间len2
从1到n枚举区间长度,同时记录前缀max,将没有被跳过的区间存到数组e中,显然e[]的区间长度len和值v都是单调递增的
离线记录询问,对询问按k值从小到大排序,用离线算法计算答案就行了(具体操作见代码)
但是n的大小最大1e5,枚举区间不可行
这题的关键信息是模数998857459是一个合数(如果没发现的话,这题直接凉了)
因此当fac[x]中的x到达一定大小的时候会变为0,那么fac[x+1]=fac[x]*(x+1)也会变成0
也就是说x到达某个数之后,后面的阶乘取模之后都是0,打表发现当x>=2803的时候,都是0
那么枚举区间端点的时候,没必要枚举0的位置,
因为给定的a[]是一个排列,那么非0的位置最多2802个,这个大小可以直接O(n2)枚举
解决了区间枚举问题之后,其他的按照前面说的做法做就行了
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
const int mod=998857459;
int fac[maxm];//阶乘
int sum[maxm];//a[i]前缀和
int pos[maxm];//存非0的位置
int ma[maxm];//ma[i]表示模mod意义下,长度为i的区间能吃出的最大值
int ans[maxm];
int a[maxm];
int n,m;
struct Q{//离线存询问
int k,id;
friend bool operator<(Q a,Q b){
return a.k<b.k;
}
}q[maxm];
struct E{//存区间
int v,len;
}e[maxm];
signed main(){
fac[0]=1;
for(int i=1;i<maxm;i++){//fac[>=2803]=0
fac[i]=fac[i-1]*i%mod;
}
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]=fac[a[i]];
if(a[i]){
pos[++cnt]=i;
}
}
for(int i=1;i<=n;i++){//前缀和
sum[i]=(sum[i-1]+a[i])%mod;
}
for(int i=1;i<=cnt;i++){//cnt<=2802
for(int j=i;j<=cnt;j++){
int t=((sum[pos[j]]-sum[pos[i]-1])%mod+mod)%mod;//[pos[i],pos[j]]的和%mod
int len=pos[j]-pos[i]+1;
ma[len]=max(ma[len],t);//更新长度为len的区间所能吃到的最大值
}
}
//预处理最优区间
int lene=0;
int maa=0;
for(int i=1;i<=n;i++){
if(ma[i]<=maa)continue;//如果ma[i]<=maa,那么选长度小的区间更优,因此直接跳过
maa=ma[i];
e[++lene]={ma[i],i};
}
for(int i=1;i<=m;i++){//离线存询问
int k;cin>>k;
q[i]={k,i};
}
sort(q+1,q+1+m);//对询问按k从小到大排序
//
int k=1;
for(int i=1;i<=m;i++){
while(k<=lene&&e[k].v<q[i].k){
k++;
}
if(k>lene){
ans[q[i].id]=-1;
}else{
ans[q[i].id]=e[k].len;
}
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<endl;
}
return 0;
}
K.Tree
题意:
解法:
dsu on tree
每个权值开一个线段树
枚举lca
参考:this
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
vector<int>g[maxm];
int sz[maxm],son[maxm],dep[maxm];
int L[maxm],R[maxm],rk[maxm],idx;
int mark[maxm];
int a[maxm];
int n,k;
int ans;
//
int lc[maxm<<5],rc[maxm<<5],cnt[maxm<<5];
int rt[maxm],tot;//rt[i]表示权值为i的线段树
void update(int x,int val,int l,int r,int &k){
if(!k)k=++tot;
cnt[k]+=val;
if(l==r)return ;
int mid=(l+r)/2;
if(x<=mid)update(x,val,l,mid,lc[k]);
else update(x,val,mid+1,r,rc[k]);
}
int ask(int st,int ed,int l,int r,int k){
if(!k)return 0;
if(st<=l&&ed>=r)return cnt[k];
int mid=(l+r)/2;
int ans=0;
if(st<=mid)ans+=ask(st,ed,l,mid,lc[k]);
if(ed>mid)ans+=ask(st,ed,mid+1,r,rc[k]);
return ans;
}
//
void dfs1(int x){
L[x]=++idx;
rk[idx]=x;
sz[x]=1;
for(int v:g[x]){
dep[v]=dep[x]+1;
dfs1(v);
sz[x]+=sz[v];
if(sz[v]>sz[son[x]]){
son[x]=v;
}
}
R[x]=idx;
}
void solve(int x,int kep){
for(int v:g[x]){//solve not son.
if(v==son[x])continue;
solve(v,0);
}
if(son[x]){
solve(son[x],1);
}
for(int v:g[x]){
if(v==son[x])continue;
for(int i=L[v];i<=R[v];i++){
int id=rk[i];
int need_val=2*a[x]-a[id];
if(need_val>=0&&need_val<=n){
int maxdep=min(n,k+2*dep[x]-dep[id]);
ans+=ask(1,maxdep,1,n,rt[need_val]);
}
}
for(int i=L[v];i<=R[v];i++){
int id=rk[i];
update(dep[id],1,1,n,rt[a[id]]);
}
}
update(dep[x],1,1,n,rt[a[x]]);//上面只加了x的所有子节点,但是没有加x,这里补上
if(!kep){//not kep
for(int i=L[x];i<=R[x];i++){
int id=rk[i];
update(dep[id],-1,1,n,rt[a[id]]);
}
}
}
signed main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=2;i<=n;i++){
int fa;cin>>fa;
g[fa].push_back(i);
}
dep[1]=1;
dfs1(1);
solve(1,1);
ans*=2;//ordered
cout<<ans<<endl;
return 0;
}
L.Who is the Champion
题意:
签到题,告诉你球赛的计分规则,要求计算出球赛的冠军,模拟一下就行了
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
int a[maxm][maxm];
int dif[maxm];
int p[maxm];
signed main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i][j]>a[j][i]){
p[i]+=3;
}else if(a[i][j]==a[j][i]){
p[i]++;
p[j]++;
}else{
p[j]+=3;
}
dif[i]+=a[i][j]-a[j][i];
dif[j]+=a[j][i]-a[i][j];
}
}
int pma=-1;
for(int i=1;i<=n;i++){
pma=max(pma,p[i]);
}
int ma=-1;
int cnt=0;
for(int i=1;i<=n;i++){
if(p[i]==pma){
if(ma==-1){
ma=i;
cnt=1;
}else{
if(dif[i]>dif[ma]){
ma=i;
cnt=1;
}else if(dif[i]==dif[ma]){
cnt++;
}
}
}
}
if(cnt!=1||ma==-1){
puts("play-offs");
}else{
cout<<ma<<endl;
}
return 0;
}