牛客小白赛23
A.不会状压,用dfs,n比较小,枚举是否消灭某一行,消灭行数上限判断 还需要消灭多少列,多处用剪枝。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
const double eps=1e-9;
using namespace std;
int n,m,a,b;
char s[25][100005];
int l[100005];///列上敌人的总数
int vis[25];///0表示没有该行没有消灭 1表示该行消灭了
bool flag;
bool check()
{
memset(l,0,sizeof(l));
int res=0;///还需要消灭的列数
for(int i=0;i<n;i++)
{
if(vis[i]==0)///只需要计算没有被消灭的行
{
for(int j=0;j<m;j++)
if(s[i][j]=='*' && l[j]==0 )
{
res++;
l[j]=1;
}
}
}
if(res<=b)
return true;
else
return false;
}
void dfs(int x,int num)///当前行 已经消灭的的行的数量
{
if(x==n)
{
if(a==num && check())///尽量消灭多的行,技能不用白不用,免得check太多超时
flag=true;
return;
}
if(flag)///一有机会就剪枝
return;
vis[x]=0;
dfs(x+1,num);///此行不消灭
if(flag)///一有机会就剪枝
return;
if(num<a)///还有消灭行的次数,此行消灭
{
vis[x]=1;
dfs(x+1,num+1);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(vis,0,sizeof(vis));
flag=false;
scanf("%d%d%d%d", &n, &m, &a, &b);
getchar();
for(int i=0;i<n;i++)
scanf("%s",s[i]);
getchar();
dfs(0,0);
if(flag)
printf("yes\n");
else
printf("no\n");
}
return 0;
}
B:先用欧拉筛打出素数表,唯一分解定理对p分解质因数,对每一个质因数x,阶乘至少要多少能解决掉这些质因数。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
const double eps=1e-9;
using namespace std;
const int maxx=1000005;
int prime[maxx];
int vis[maxx];
int num[32005];
int cnt;
int p;
void init()
{
memset(vis,true,sizeof(vis));
vis[0]=vis[1]=false;
cnt=0;
for(int i=2;i<=maxx;i++)
{
if(vis[i])
prime[cnt++]=i;
for(int j=0;j<cnt && prime[j]*i<=maxx;j++)
{
vis[ i*prime[j] ]=false;
if(i%prime[j]==0) break;
}
}
}
int find(int k,int x)///找k个x,阶乘至少要多少,x是素数
{
int i=0;
int now=0;//目前提供了多少k
for(i=x;k>0;i=i+x){
//printf("i=%d k=%d x=%d\n",i,k,x);
int temp=i;
while(temp%x==0){
k--;
temp=temp/x;
}
}
return i-x;
}
int main()///对p分解质因子
{
init();/*
int ans=find(7,2);
printf("ans=%d\n",ans);*/
int t;
scanf("%d",&t);
while(t--){
scanf("%d",&p);///p<1e9
memset(num,0,sizeof(num));///质因子的数量
int maxx=0;
for(int i=0;prime[i]<32000 && prime[i]<=p;i++){
int temp=0;
while(p%prime[i]==0){
p=p/prime[i];
temp++;
}
maxx=max(maxx,find(temp,prime[i]));
//printf("sushu=%d temp=%d maxx=%d\n",prime[i],temp,maxx);
}
if(p>0)///大素数
maxx=max(maxx,p);
printf("%d\n",maxx);
}
return 0;
}
C:完全图是指每两个点都有边直接相连的图,分离出第一个联通分量需要删除n-1条边,第二个需要删除n-2,以此类推直到最后两个点删除1条边。等差求和二分查找项数,变量太大开启Java大数杀招。
import java.math.BigInteger;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
BigInteger two=BigInteger.valueOf(2);
int t=scan.nextInt();
while(t>0) {
t--;
long n=scan.nextLong();
long m=scan.nextLong();
long l=1,r=n-1,mid=0,ans=0;;
//完全图,任意两点都有边,分离出一个第一个连通分量要删n-1,第二个n-2
//n-1 n-2 n-3 ... 1 等差求和 有x项
//(n-1 + n-x) * x / 2
while(l<=r) {
mid=(l+r)/2;//项数
BigInteger sum=BigInteger.valueOf(2*n-1-mid).multiply(BigInteger.valueOf(mid)).divide(two);
//sum=(2*n-1-mid)*mid/2
//System.out.println("l="+l+" r="+r+" mid="+mid+" sum="+sum);
if(sum.compareTo(BigInteger.valueOf(m))<=0) {
ans=mid;
l=mid+1;
}else
r=mid-1;
}
System.out.println(ans+1);
}
}
}
E:类似这种题,把代码放到编译器运行一下会发现答案都一样。
G:记录每条边需要用多少次,用得多的赋值小一点的权值。dfs中,记录以x为根的子树的规模大小,一条边用的次数=两点的子树规模相乘。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
const double eps=1e-9;
using namespace std;
vector<int>a[100005];
int vis[100005];
int num[100005];
ll edge[100005];
int t,n,k,idx;
struct node{
int x;
int val;
};
void dfs(int x,int last)///当前点,父节点
{
num[x]=1;
int len=a[x].size();
for(int i=0;i<len;i++){
int v=a[x][i];
if(v!=last){
dfs(v,x);
num[x]+=num[v];///以u为根节点的子树的规模
///edge[idx++]=1ll*num[x]*num[v];//不可以这样写,因为num[x]还在累加之中
edge[idx++]=1ll*num[v]*(n-num[v]);///x和v这条边用过的次数=两边子树的大小相乘
}
}
}
int main()
{
scanf("%d",&n);
idx=0;
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs(1,-1);
sort(edge,edge+idx);
ll ans=0;
for(int i=0;i<idx;i++)
ans=ans+(--n)*edge[i];
printf("%lld\n",ans);
return 0;
}
H:全部看出二进制,手动模拟一下进位,从大到小填背包,能填满的话第一次肯定会增加到230
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
const double eps=1e-9;
using namespace std;
int two[31];
struct node{
int x;
int id;
int vis;
};
node k[100005];
int m;
void init(){
two[0]=1;
for(int i=1;i<31;i++)
two[i]=2*two[i-1];
}
bool cmp(node p1,node p2){
return p1.id<p2.id;
}
bool cmp2(node p1,node p2){
return p1.x<p2.x;
}
int main()
{
init();
int t;
scanf("%d",&t);
bool flag;
while(t--){
flag=false;
scanf("%d",&m);
for(int i=0;i<m;i++)
scanf("%d",&k[i].x),k[i].id=i,k[i].vis=0;
sort(k,k+m,cmp2);
ll sum=two[30];
ll now=0;
for(int i=m-1;i>=0 && now<sum;i--){
now+=two[ k[i].x ];
k[i].vis=1;
if(now==sum)
flag=true;
}
if(flag){
sort(k,k+m,cmp);
for(int i=0;i<m;i++)
printf("%d",k[i].vis);
printf("\n");
}else
printf("impossible\n");
}
return 0;
}
I:优先比较前缀字母,相同前缀的话,后缀越长,字典序越大,暴力找片段,ans数组存储答案,每次加进一个能使字典序最大的字母。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
const double eps=1e-9;
using namespace std;
char s[1005];
char ans[1005];
int cnt;
int n;
bool check(int a){//起始位置
bool flag=true;
for(int i=1;i<=cnt;i++){
if( a+i-1<n && ans[i]==s[a+i-1] ){
// printf("check if i=%d a+i-1=%d n=%d\n",i,a+i-1,n);
}else{
flag=false;
}
}
return flag;
}
int main()
{
scanf("%s",s);
n=strlen(s);
char maxx=s[0];
for(int i=0;i<n;i++){
if(s[i]>maxx)
maxx=s[i];
}
ans[1]=maxx;//先放一个最大的,后续检查前缀相同,每次添加一个最大的字母
cnt=1;
bool flag=true;
while(flag){
flag=false;
for(int i=0;i<n;i++)
{
if(s[i]==ans[1] && check(i))
{//第一次只有1
if(i+cnt<n && ans[cnt+1]<s[i+cnt])
ans[cnt+1]=s[i+cnt],flag=true;
}
}
if(flag)
cnt++;
}
for(int i=1;i<=cnt;i++)
printf("%c",ans[i]);
printf("\n");
return 0;
}
J:最大-最小