191021-基础测试1
T1 特殊排序
题目描述
写一个程序,对于读入的一对正整数x和y,将这两个数之间(包括这两个数本身)的所有数按下述特别规则排序后输出,该特别规则是按两数倒过来的值进行比较决定其大小,如30倒过来为3,29倒过来为92,则29大于30。
如果倒过来的数和原来的某一个数一样大,则按原来的顺序从小到大排序。比如:原始的数有11和110,则110倒过来是新11,和原来的11相等,则输出的时候11放110前面。
输入格式
输入一行两个正整数x 和 y ,用一个空格隔开(1≤x≤y≤999999999,y-x≤100)。
输出格式
输出输出包括y-x+1行,每行一个正整数,按两数倒过来的值进行比较决定其大小,然后由小到大输出
分析
用结构体的两个域存储原始数和反向数。 按反向数排序后,依次输出原始数
注意
如果倒过来,两者值相等,其相对位置不能改变。
题解
#include<bits/stdc++.h>
using namespace std;
int cnt,len,q[10009],a,b;
struct zb
{
int x,y;
}m[10009];
bool comp(const zb &a,const zb &b)
{
if(a.x!=b.x) return a.x<b.x;
return a.y<b.y;
}
void make(int r)
{
cnt++;
m[cnt].y=r;
len=0;
while(r)
{
q[++len]=r%10;
r/=10;
}
for(int i=1;i<=len;i++)
m[cnt].x=m[cnt].x*10+q[i];
}
int main()
{
freopen("ssort.in","r",stdin);
freopen("ssort.out","w",stdout);
scanf("%d%d",&a,&b);
for(int i=a;i<=b;i++)
make(i);
sort(m+1,m+b-a+2,comp);
for(int i=1;i<=b-a+1;i++)
printf("%d\n",m[i].y);
return 0;
}
T2 部落卫队
题目描述
原始部落 byteland 中的居民们为了争夺有限的资源,经常发生冲突。几乎每个居民都有他的仇敌。部落酋长为了组织一支保卫部落的队伍,希望从部落的居民中选出最多的居民入伍,并保证队伍中任何 2 个人都不是仇敌。
给定 byteland 部落中居民间的仇敌关系,编程计算组成部落卫队的最佳方案。
输入格式
第 1 行有 2 个正整数 n(1<=n<=100) 和 m(1<=n<=3000) ,表示byteland部落中有 n 个居民,居民间有 m 个仇敌关系。居民编号为 1,2,…,n。接下来的 m 行中,每行有2个正整数 u 和 v,表示居民 u 与居民 v 是仇敌。
输出格式
第1行是部落卫队的顶人数;
第2行是卫队组成 xi ,1≤i≤n, 其中 xi =0 表示居民i不在卫队中,xi=1表示居民i在卫队中。
分析
一道搜索原题(但还是没做出来,写的贪心),虽然数据达到了100,但只要加上最优化剪枝就不会t掉。另外一个技巧,f[i]数组相当于一个计数器,表示在已经确定的部落卫队的人中,以i为仇敌的人数,有且只有当f[i]==0时,才能加入卫队。
题外话
该题还能用随机化贪心,用到的函数为random_shuffle(b+1,b+n+1)将原来的数组随机打乱,然后我们按照打乱后的顺序来选,重复200000次,便无线接近于正确答案,代码如下
题解(搜索剪枝)
#include<bits/stdc++.h>
using namespace std;
int maxn,ans,d[109],b[109],a[109][109],n,m,f[109];
void dfs(int dep)
{
if(dep==n+1&&ans<maxn)
{
ans=maxn;
for(int i=1;i<=n;i++)
d[i]=b[i];
return;
}
if(maxn+n-dep+1<ans) return;
if(!f[dep])
{
maxn++;
b[dep]=1;
for(int i=1;i<=a[dep][0];i++) f[a[dep][i]]+=1;
dfs(dep+1);
for(int i=1;i<=a[dep][0];i++) f[a[dep][i]]-=1;
maxn--;
}
b[dep]=0;
dfs(dep+1);
}
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
a[x][++a[x][0]]=y;
a[y][++a[y][0]]=x;
}
dfs(1);
printf("%d\n",ans);
for(int i=1;i<=n;i++)
printf("%d ",d[i]);
return 0;
}
附上错误贪心代码
#include<bits/stdc++.h>
using namespace std;
int n,m,bj[101],a[109][109],num,ans[109];
priority_queue<pair<int,int> >q;
int main()
{
int x,y;
//freopen("guard.in","r",stdin);
//freopen("guard.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
a[x][++a[x][0]]=y;
a[y][++a[y][0]]=x;
}
for(int i=1;i<=n;i++)
q.push(make_pair(-a[i][0],i));
while(!q.empty())
{
int v=q.top().second;
q.pop();
if(!bj[v])
{
ans[v]=1;
num++;
for(int i=1;i<=a[v][0];i++)
bj[a[v][i]]=1;
}
}
printf("%d\n",num);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}
随机化贪心代码(可以ac)
#include<bits/stdc++.h> //没有判断字典序最大
using namespace std;
int a[101][101],c[101],bj[101],b[101],n,m,maxn=-100,ans,vis[101];
int main()
{
int x,y;
scanf("%d%d",&n,&m);;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
a[x][++a[x][0]]=y;
a[y][++a[y][0]]=x;
}
for(int i=1;i<=n;i++)
b[i]=i;
for(int cas=1;cas<=200000;cas++)
{
ans=0;
memset(vis,0,sizeof(vis));
memset(bj,0,sizeof(bj));
random_shuffle(b+1,b+n+1);
for(int i=1;i<=n;i++)
{
if(bj[b[i]]) continue;
bj[b[i]]=1;
vis[b[i]]=1;
ans++;
for(int j=1;j<=a[b[i]][0];j++)
bj[a[b[i]][j]]=1;
}
if(ans>=maxn)
{
maxn=ans;
for(int i=1;i<=n;i++)
c[i]=vis[i];
}
}
printf("%d",maxn);
for(int i=1;i<=n;i++)
printf("%d ",c[i]);
return 0;
}
T3 数字排序
题目描述
国安局情报人员收到了一些特殊的加密信息,这些信息由两个数列 A 和 B 构成,A 中有 N 个数,B 中有 M 个数,把 A 中每一个数分别与 B 中每一个数乘起来得到的 N×M 个数进行从小到大排序后,其中的第 K 个数就是加密信息的原文数字。现在请你帮他解密。
输入格式
第一行是三个整数 N(1≤N≤10000), M(1≤M≤10000), K(1≤K≤N×M)。相邻两个数由一个空格隔开。
第二行是 N 个属于 A 数列的整数 Ai(0≤Ai≤10000),相邻两个数由一个空格隔开。
第三行是 M 个属于 B 数列的整数 Bi(0≤Bi≤10000),相邻两个数由一个空格隔开。
输出格式
输出一个整数,即满足题意的第 K 个数。
分析
二分套二分。设 C=A×B,当 A 是固定 且为非负整数的时候,当 B 增加时,C 不减小,即是函数具有单调性。 因此这个题目可以先将两个数列 A 和 B 分别进行升序排序,然后枚举一个数列 A 的每一 个数,得到 C/A[i],接着通过二分查找(可以用upper_bound函数),在 B 中找出小于或等于 C/A[i]的数有多少个,把 这些个数进行求和,再与 K 比较即可判断二分的上界和下界的变化。二分完成后得到的就是正确答案。
最后分析一下本题的时间复杂度。这种解法总共需要二分答案 log2(max{ai×bi})次, 每次二分答案枚举数组 A 和二分查找数组 B,共操作 n×log2m 次,因此时问复杂度为 O(log2(max{ai×bi})×n×log2m)。
题解(使用upper_bound)
#include<bits/stdc++.h>
using namespace std;
int a[10009],b[10009],n,m,k;
bool check(int r)
{
int ans=0;
for(int i=1;i<=n;i++)
{
if(a[i]==0)//特判a[i]==0的情况;
{
ans+=m;
continue;
}
int z=r/a[i];
ans=ans-1+upper_bound(b+1,b+m+1,z)-b;
}
if(ans<k) return true;
return false;
}
int main()
{
freopen("numsort.in","r",stdin);
freopen("numsort.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
scanf("%d",&b[i]);
sort(a+1,a+n+1);
sort(b+1,b+m+1);
int l=a[1]*b[1];
int r=a[n]*b[m];
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))//比第k小数小
l=mid+1;
else
r=mid;
}
printf("%d",l);
return 0;
}
不使用upper_bound
#include<bits/stdc++.h>
using namespace std;
int na,nb,k; //数列A,数列B的大小和K
int a[10005],b[10005]; //数列A和数列B
int Find(int x) //在B中通过二分查找,求小于等于x的数的个数
{ //先找小于等于x的最大值Max,则个数=Max的下标L
int L=1,R=nb,mid; //B数组下标的上界、下界、中间值
while(L<R)
{
mid=(L+R+1)>>1; //得到中间值
if(b[mid]<=x) //b[mid]小于等于x时,在右半区间找(增加),要找最大的
L=mid;
else //b[mid]大于x时,在有左半区间找(减少)
R=mid-1;
}
return L; //返回数列B中小于等于x的数的个数
}
int Count(int x) //x为main中二分到的答案
{
int ret=0;
for(int i=1;i<=na;i++) //枚举A中每个数
{
if(a[i]==0) //如果a[i]为零,
ret+=nb; //则a[i]与B中每个数相乘都必然小于等于非负整数x,所以个数为nb
else
ret+=Find(x/a[i]); //如果a[i]不为零,则用Find求出B中小于等于x/a[i]的个数并累加
}
return ret; //返回累加的个数
}
int main()
{
freopen("numsort.in","r",stdin);
freopen("numsort.out","w",stdout);
scanf("%d%d%d",&na,&nb,&k); //读入A,B的大小和k
for(int i=1;i<=na;i++) scanf("%d",&a[i]); //读入数列A
for(int i=1;i<=nb;i++) scanf("%d",&b[i]); //读入数列B
sort(a+1,a+na+1); //对A进行排序
sort(b+1,b+nb+1); //对B进行排序
int L=a[1]*b[1]; //二分的下届
int R=a[na]*b[nb]; //二分的上界
int mid; //中间值
while(L<R)
{
mid=(L+R)>>1;
if(Count(mid)>=k) //如果统计出来的个数大于等于K
R=mid; //减少C
else
L=mid+1; //增加C
}
printf("%d\n",L); //输出答案(第K小值)
return 0;
}
T4 单词
题目背景
TJOI2013 DAY1 T3
题目描述
小张最近在忙毕业论文设计,所以一直在读论文。一篇论文是由许多单词组成的。但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现多少次。
输入格式
第一行一个整数 N (N≤200),表示有 N 个单词。接下来 N 行每行一个单词。每个单词都由小写字母(‘a’~’z’)组成。
所有单词构成论文(一行一个单词)。
输出格式
输出 N 个整数,第 i 行的数字表示第 i 个单词在文章中出现了多少次。
分析
后缀数组或 AC 自动机均可解决本题。 我们使用 AC 自动机实现。注意要记录单词的出现位置和结束位置,但不能在最后查找, 因为单词出现的次数是从它出现的位置开始算起,即所有单词合成的这篇文章中,处于当前单词之前可能还合成了当前单词,但不计入最后结果。 题目略微表述不清,论文不是所有单词连接在一起的,而是连接处有空格(可从样例看出来)
题解1(AC自动机)
//AC自动机
#include<bits/stdc++.h>
using namespace std;
vector<int>loc,order;
int n,nxt[1000010][30],fail[1000010],sum[1000010],tot;
bool vis[1000010];
void clean()
{
memset(sum,0,sizeof(sum));
memset(fail,0,sizeof(fail));
memset(nxt,0,sizeof(nxt));
memset(vis,false,sizeof(vis));
tot=0;
loc.clear(); order.clear();
return;
}
void insert(char c[])
{
int len=strlen(c);
int now=0,i;
for (i=0;i<len;++i)
{
int x=c[i]-'a';
if (!nxt[now][x]) nxt[now][x]=++tot;
sum[nxt[now][x]]++; now=nxt[now][x];
}
vis[now]=true; //标记以当前点为结尾的单词出现过
loc.push_back(now); //标记结尾
return;
}
void buildfail()
{
queue<int>que;
int i,j;
for(i=0;i<26;++i)
if(nxt[0][i]) que.push(nxt[0][i]);
while(!que.empty())
{
int now=que.front(); que.pop();
order.push_back(now); //把当前出现的单词存起来
for(i=0;i<26;++i)
{
if(!nxt[now][i])
{
nxt[now][i]=nxt[fail[now]][i];
continue;
}
int x=nxt[now][i];
fail[x]=nxt[fail[now]][i]; //fail里存到当前字母为止的单词的最长后缀
que.push(x); //将更新出来的单词存入队列(存的是序号)
}
}
return;
}
void find()
{
for(int i=order.size()-1;i>=0;--i)
sum[fail[order[i]]]+=sum[order[i]];
for(int i=0;i<n;++i)
printf("%d\n",sum[loc[i]]);
return;
}
int main()
{
freopen("word.in","r",stdin);
freopen("word.out","w",stdout);
clean();
scanf("%d",&n);
for(int i=0;i<n;++i)
{
char s1[1000010];
scanf("%s",s1);
insert(s1);
}
buildfail();
find();
return 0;
}
题解2(后缀数组)
#include<string.h>
#include<stdio.h>
#include<algorithm>
#define MAXN 2100000
using namespace std;
class DC3{
public:
char* ch;
int *rank,*SA;
int n;
int cnt0,cnt1,cnt2;
static int sum[MAXN];
static int tmpSA[MAXN];
static int tmpRank[MAXN];
DC3(){}
DC3(int* _rank,int* _SA,int _n,char* s=NULL){
rank=_rank;
SA=_SA;
n=_n;
rank[n]=rank[n+1]=-1;
if(s==NULL) return;
for(int i=0;i<n;i++)
rank[i]=s[i];
ch=s;
}
void getH(int* h){
for(int i=0;i<n;i++){
if(rank[i]==0){
h[i]=0;
}
else {
h[i]=(i?max(0,h[i-1]-1):0);
for(;h[i]<n&&ch[i+h[i]]==ch[SA[rank[i]-1]+h[i]];h[i]++);
}
}
}
void radixSort(int* rank,int* SA,int n){
int maxKey=0;
for(int i=0;i<n;i++){
sum[rank[SA[i]]+2]++;
maxKey=max(maxKey,rank[SA[i]]+2);
}
for(int i=1;i<=maxKey+1;i++)
sum[i]+=sum[i-1];
for(int i=0;i<n;i++){
tmpSA[sum[rank[SA[i]]+1]++] = SA[i];
}
memcpy(SA,tmpSA,sizeof(int)*n);
memset(sum,0,sizeof(int)*(maxKey+2));
}
int convert(int x){
if(x<cnt1)return x*3+1;
return (x-cnt1-1)*3+2;
}
int co_convert(int x){
if(x%3==1)return x/3;
return x/3+cnt1+1;
}
bool compare(int i,int j,int* nextRank){
if(rank[i]!=rank[j])return rank[i]<rank[j];
if(i%3+j%3==3)return nextRank[co_convert(i)]<nextRank[co_convert(j)];
return compare(i+1,j+1,nextRank);
}
bool operator ()(int a,int b){
if(a==b)return false;
while(rank[a]==rank[b])++a,++b;
return rank[a]<rank[b];
}
void solve(){
for(int i=0;i<n;i++)
SA[i]=i;
if(n<=100){
sort(SA,SA+n,*this);
for(int i=0;i<n;i++)
rank[SA[i]]=i;
return;
}
radixSort(rank+2,SA,n);
radixSort(rank+1,SA,n);
radixSort(rank,SA,n);
tmpRank[SA[0]]=0;
for(int i=1;i<n;i++)
tmpRank[SA[i]]=tmpRank[SA[i-1]]+
(rank[SA[i]]!=rank[SA[i-1]]||
rank[SA[i]+1]!=rank[SA[i-1]+1]||
rank[SA[i]+2]!=rank[SA[i-1]+2]);
memcpy(rank,tmpRank,sizeof(int)*n);
if(rank[SA[n-1]]==n-1)return;
int* nextRank=rank+n+2;
int nextN=0;
cnt1=cnt2=0;
for(int i=1;i<n;i+=3){
nextRank[nextN++]=rank[i]+1;
cnt1++;
}
nextRank[nextN++]=0;
for(int i=2;i<n;i+=3){
nextRank[nextN++]=rank[i]+1;
cnt2++;
}
cnt0=n-cnt1-cnt2;
int* nextSA=SA+n;
DC3 next(nextRank,nextSA,nextN);
next.solve();
for(int i=0;i<nextN;i++){
if(nextRank[i]==0)continue;
tmpRank[convert(i)]=nextRank[i];
}
for(int i=0;i<n;i+=3)
SA[i/3]=i;
radixSort(tmpRank+1,SA,cnt0);
radixSort(rank,SA,cnt0);
memcpy(tmpSA,SA,sizeof(int)*cnt0);
for(int i=1,j=0,cnt=0;i<=nextN;i++){
while(j<cnt0&&(i==nextN||compare(tmpSA[j],convert(nextSA[i]),nextRank))){
SA[cnt++]=tmpSA[j++];
}
SA[cnt++]=convert(nextSA[i]);
}
for(int i=0;i<n;i++)
rank[SA[i]]=i;
}
};
int DC3::sum[MAXN]={};
int DC3::tmpSA[MAXN];
int DC3::tmpRank[MAXN];
int rank[3*MAXN],SA[3*MAXN],h[MAXN];
char ch[MAXN];
int st[MAXN];
int len[MAXN];
int half[MAXN];
int rmq[MAXN][20];
void build_rmq(int L){
half[0]=0;
for(int i=1;i<=L;i++){
half[i]=half[i-1];
if((2<<half[i])==i)half[i]++;
}
for(int i=0;i<L;i++){
rmq[i][0]=h[SA[i]];
}
for(int r=1;r<20;r++){
for(int i=0;i+(1<<r)<=L;i++)
rmq[i][r]=min(rmq[i][r-1],rmq[i+(1<<(r-1))][r-1]);
}
}
int query(int fr,int to){
if(fr==to)return 987654321;
int L=half[to-fr];
fr++;
return min(rmq[fr][L],rmq[to-(1<<L)+1][L]);
}
int main(){
int n,L=0;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",ch+L);
st[i]=L;
len[i]=strlen(ch+L);
L+=len[i]+1;
ch[L-1]='a'-1;
}
DC3 dc3=DC3(rank,SA,L,ch);
dc3.solve();
dc3.getH(h);
build_rmq(L);
for(int i=0;i<n;i++){
int x=rank[st[i]];
int fr=0,to=L-1;
int tmp=x;
while(tmp-fr>1){
int mid=(tmp+fr)/2;
if(query(mid,x)>=len[i])tmp=mid;
else fr=mid;
}
if(query(x,to)>=len[i])to++;
else {
tmp=x;
while(to-tmp>1){
int mid=(tmp+to)/2;
if(query(x,mid)>=len[i])tmp=mid;
else to=mid;
}
}
printf("%d\n",to-fr-1);
}
}
附上ac自动机模板
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000010;
const int maxm=50*10010;
char t[60],s[maxn];
int n;
int ch[maxm][26]; //Trie树节点
int val[maxm]; //每个字符串的结尾节点都有一个非0的val
int fail[maxm]; //前缀指针,fail[i]表示i的前缀指针
int last[maxm]; //last[i]=j,j节点表示的单词是i节点单词的后缀,且j节点是单词节点
int sz; //节点编号
void insert(char *s) //构建Trie树
{
int u=0;
int n=strlen(s);
for(int i=0;i<n;i++)
{
int c=s[i]-'a'; //把a..z转为0..25
if(!ch[u][c])
{
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
}
val[u]++; //val[u]表示节点0~节点u构成的单词数量
}
void getfail() //计算前缀指针指向v
{
queue<int> q; //普通队列
fail[0]=0; //根
int u=0;
for(int i=0;i<26;i++) //根的26个节点的前缀指针都指向根,并入队
{
u=ch[0][i];
if(u)
{
q.push(u); //入队
fail[u]=0;
last[u]=0;
}
}
while(!q.empty()) //广搜计算fail
{
int r=q.front();q.pop(); //队首r出队
for(int i=0;i<26;i++) //枚举r的26个子节点u,求fail[u]
{
u=ch[r][i];
if(!u) //如果u不存在,则跳向其父节点r前缀指针指向的第i个子节点
{
ch[r][i]=ch[fail[r]][i];
continue; //继续枚举r的下一个子节点
}
q.push(u); //如果u存在,则入队
int v=fail[r];
while(v&&!ch[v][i]) v=fail[v]; //v存在并且v没有第i个子节点,则跳到v的前缀指针指向的节点
fail[u]=ch[v][i]; //v存在并且v的第i个子节点也存在,就可以确定前缀指针
last[u]=val[fail[u]]?fail[u]:last[fail[u]];
} //fail[u]是一个完整单词且是u的后缀 否则
}
}
int find(char *s) //在s中查找出现了哪几个模板的单词
{
int u=0,cnt=0; //从根开始,cnt为答案
int n=strlen(s);
for(int i=0;i<n;i++) //扫描文章中的每个字符,
{
int c=s[i]-'a'; //把a..z转为0..25
u=ch[u][c];
int temp=0; //必须赋初值为0,否则如果下面两个判断都不成立,while也可正常执行
if(val[u]) //如果u是一个完整单词
temp=u;
else if(last[u]) //如果u的后缀last[u]存在
temp=last[u];
while(temp)
{
cnt+=val[temp]; //累加单词数量
val[temp]=0; //标记,加过了不能再加
temp=last[temp]; //跳到当前temp的后缀(比temp短),更新temp
}
}
return cnt;
}
int main()
{
//freopen("ks.in","r",stdin);
//freopen("ks.out","w",stdout);
memset(ch[0],0,sizeof(ch[0])); //初始化0号节点的相关信息(多组数据,每次需要初始化)
memset(last,0,sizeof(last));
sz=1;
scanf("%d",&n); //读入n个单词
for(int i=1;i<=n;i++) //构建Trie树
{
scanf("%s",t);
insert(t);
}
getfail(); //计算前缀指针
scanf("%s",s); //读入文章
int ans=find(s); //查找
printf("%d\n",ans);
return 0;
}